/* Copyright (c) Microsoft Corporation.
   Licensed under the MIT License. */

/***************************************************************************

	utest.cpp: Socrates main app class

	Primary Author: ******
	Review Status: REVIEWED - any changes to this file must be reviewed!

	The APP class handles initialization of the product, and global
	actions such as resolution-switching, switching between the building
	and the studio, and quitting.

	The KWA (App KidWorld) is the parent of the gob tree in the product.
	It also is used to display a splash screen, and to find AVIs on the CD.

***************************************************************************/
#include "studio.h"
#include "socres.h"
#include "mminstal.h"

ASSERTNAME

// If the following value is defined, 3DMM displays the dlidDesktopResizing 
// and dlidDesktopResized dialogs before and immediately after res-switching.
// The current thought is that these dialogs are unnecessary since we only
// res-switch to 640x480, which should always be safe.  We still display a
// dialog if the res-switch failed (dlidDesktopWontResize).
// #define RES_SWITCH_DIALOGS 


// If the following value is defined, 3DMM does a run-time performance test
// of the graphics, fixed-point math, and copying speed at startup and sets
// _fSlowCPU to fTrue if it thinks 3DMM is running on a slow computer.
// The current feeling is that we don't have the resources to tweak the 
// thresholds and verify that this gives us the result we want on all 
// computers.
//#define PERF_TEST

// Duration to display homelogo
const ulong kdtsHomeLogo = 4 * kdtsSecond;	

// Duration to display splash screen
const ulong kdtsSplashScreen = 4 * kdtsSecond;

// Duration before res-switch dialog cancels itself
const ulong kdtsMaxResSwitchDlg = 15 * kdtsSecond;

// 2MB cache per source for TAGM
const ulong kcbCacheTagm = 2048 * 1024;

static PSZ kpszAppWndCls = PszLit("3DMOVIE");
const PSZ kpszOpenFile = PszLit("3DMMOpen.tmp");

const long klwOpenDoc = 0x12123434; // arbitrary wParam for WM_USER

BEGIN_CMD_MAP(APP, APPB)
	ON_CID_GEN(cidInfo, FCmdInfo, pvNil)
	ON_CID_GEN(cidLoadStudio, FCmdLoadStudio, pvNil)
	ON_CID_GEN(cidLoadBuilding, FCmdLoadBuilding, pvNil)
	ON_CID_GEN(cidTheaterOpen, FCmdTheaterOpen, pvNil)
	ON_CID_GEN(cidTheaterClose, FCmdTheaterClose, pvNil)
	ON_CID_GEN(cidPortfolioOpen, FCmdPortfolioOpen, pvNil)
	ON_CID_GEN(cidPortfolioClear, FCmdPortfolioClear, pvNil)
	ON_CID_GEN(cidDisableAccel, FCmdDisableAccel, pvNil)
	ON_CID_GEN(cidEnableAccel, FCmdEnableAccel, pvNil)
	ON_CID_GEN(cidInvokeSplot, FCmdInvokeSplot, pvNil)
	ON_CID_GEN(cidExitStudio, FCmdExitStudio, pvNil)
	ON_CID_GEN(cidDeactivate, FCmdDeactivate, pvNil)
END_CMD_MAP_NIL()


APP vapp;
PTAGM vptagm;

RTCLASS(APP)
RTCLASS(KWA)

/***************************************************************************
	Entry point for a Kauai-based app.
***************************************************************************/
void FrameMain(void)
{
	Debug( vcactAV = 2; ) // Speeds up the debug build
	vapp.Run(fappOffscreen, fgobNil, kginMark);
}


/******************************************************************************
	Run
		Overridden APPB::Run method, so that we can attempt to recover
		gracefully from a crash.
	
	Arguments:
		ulong grfapp -- app flags
		ulong grfgob -- GOB flags
		long ginDef  -- default GOB invalidation
	
************************************************************ PETED ***********/
void APP::Run(ulong grfapp, ulong grfgob, long ginDef)
{
	/* Don't bother w/ AssertThis; we'd have to use AssertBaseThis, or
		possibly the parent's AssertValid, which gets done almost right
		away anyway */

	_CleanupTemp();

	__try
		{
		APP_PAR::Run(grfapp, grfgob, ginDef);
		}
	__except (UnhandledExceptionFilter(GetExceptionInformation()))
		{
		PDLG pdlg;

		pdlg = DLG::PdlgNew(dlidAbnormalExit, pvNil, pvNil);
		if (pdlg != pvNil)
			{
			pdlg->IditDo();
			ReleasePpo(&pdlg);
			}

		_fQuit = fTrue;
		MVU::RestoreKeyboardRepeat();
#ifdef WIN
		ClipCursor(NULL);
#endif // WIN
		_CleanUp();
		}
}


/***************************************************************************
	Get the name for the app.  Note that the string comes from a resource
	in the exe rather than a chunky file since the app name is needed
	before any chunky files are opened.  The app name should not change
	even for different products, i.e., Socrates and Playdo will have
	different _stnProduct, but the same _stnAppName.
***************************************************************************/
void APP::GetStnAppName(PSTN pstn)
{
	AssertBaseThis(0);
	AssertPo(pstn, 0);

	if (_stnAppName.Cch() == 0)
		{
#ifdef WIN
		SZ sz;

		if (0 != LoadString(vwig.hinst, stidAppName, sz, kcchMaxSz))
			_stnAppName = sz;
		else
			Warn("Couldn't read app name");
#else //MAC
		RawRtn();
#endif //MAC
		}
	*pstn = _stnAppName;
}


#ifdef DEBUG
struct DBINFO
	{
	long cactAV;
	};
#endif // DEBUG


/***************************************************************************
	Init the APP
***************************************************************************/
bool APP::_FInit(ulong grfapp, ulong grfgob, long ginDef)
{
	AssertBaseThis(0);

	ulong tsHomeLogo;
	ulong tsSplashScreen;
	FNI fniUserDoc;
	long fFirstTimeUser;

	// Only allow one copy of 3DMM to run at a time:
	if (_FAppAlreadyRunning())
		{
		_TryToActivateWindow();
		_fDontReportInitFailure = fTrue;
		goto LFail;
		}

#ifdef DEBUG
	{
	DBINFO dbinfo;

	dbinfo.cactAV = vcactAV;
	if (FGetSetRegKey(PszLit("DebugSettings"), &dbinfo, size(DBINFO),
			fregSetDefault | fregBinary))
		{
		vcactAV = dbinfo.cactAV;
		}
	}
#endif // DEBUG

	if (!_FEnsureOS())
		goto LFail;

	if (!_FEnsureAudio())
		goto LFail;

	if (!_FEnsureVideo())
		goto LFail;

	_ParseCommandLine();

	// If _ParseCommandLine doesn't set _stnProduct, set to 3dMovieMaker
	if (!_FEnsureProductNames())
		goto LFail;

	if (!_FFindMsKidsDir())
		goto LFail;

	// Init product names for tagman & _fniProductDir & potentially _stnProduct
	if (!_FInitProductNames())
		goto LFail;
					
	if (!_FOpenResourceFile())
		goto LFail;

	if (!_FReadStringTables())
		goto LFail;

	if (!_FEnsureDisplayResolution()) // may call _FInitOS
		goto LFail;
		
	if (!_FEnsureColorDepth())
		goto LFail;

	if (!APP_PAR::_FInit(grfapp, grfgob, ginDef)) // calls _FInitOS
		goto LFail;

	/* Ensure default font.  Do it here just so we get the error reported
		early. */
	OnnDefVariable();

	if (!_FInitKidworld())
		goto LFail;

	if (!_FDisplayHomeLogo())
		goto LFail;
	tsHomeLogo = TsCurrent();

	if (!_FDetermineIfSlowCPU())
		goto LFail;

	if (!_FInitTdt())
		goto LFail;

	if (!MTRL::FSetShadeTable(_pcfl, kctgTmap, 0))
		goto LFail;

	while (TsCurrent() - tsHomeLogo < kdtsHomeLogo)
		; // spin until home logo has been up long enough

	if (!_FShowSplashScreen())
		goto LFail;
	tsSplashScreen = TsCurrent();

	if (!_FPlaySplashSound())
		goto LFail;

	if (!_FGetUserName())
		goto LFail;

	if (!_FGetUserDirectories())
		goto LFail;

	if (!_FReadUserData())
		goto LFail;

	if (!_FInitCrm())
		goto LFail;

	while (TsCurrent() - tsSplashScreen < kdtsSplashScreen)
		; // spin until splash screen has been up long enough
	Pkwa()->SetMbmp(pvNil); // bring down splash screen

	// If the user specified a doc on the command line, go straight
	// to the studio.  Otherwise, start the building.
	GetPortfolioDoc(&fniUserDoc);

	if (fniUserDoc.Ftg() == ftgNil)
		{
		// Startup place depends on whether this is the user's first time in.
		if (!FGetProp(kpridFirstTimeUser, &fFirstTimeUser))
			goto LFail;

		if (!FSetProp(kpridBuildingGob, (fFirstTimeUser ? kgobCloset : kgobLogin)))
			goto LFail;
		if (!_FInitBuilding())
			goto LFail;
		}
	else
		{
		if (!FSetProp(kpridBuildingGob, kgobStudio1))
			goto LFail;
		if (!_FInitStudio(&fniUserDoc, fFalse))
			goto LFail;
		}

	EnsureInteractive();

	return fTrue;
LFail:
	_fQuit = fTrue;

	if (_fSwitchedResolution)
		{
		if (_FSwitch640480(fFalse)) // try to restore desktop
			_fSwitchedResolution = fFalse;
		}

	// _fDontReportInitFailure will be true if one of the above functions
	// has already posted an alert explaining why the app is shutting down.
	// If it's false, we put up a OOM or generic error dialog.
	if (!_fDontReportInitFailure)
		{
		PDLG pdlg;

		if (vpers->FIn(ercOomHq) || vpers->FIn(ercOomPv) || vpers->FIn(ercOomNew))
			pdlg = DLG::PdlgNew(dlidInitFailedOOM, pvNil, pvNil);
		else
			pdlg = DLG::PdlgNew(dlidInitFailed, pvNil, pvNil);
		if (pvNil != pdlg)
			pdlg->IditDo();
		ReleasePpo(&pdlg);
		}
	return fFalse;
}


/******************************************************************************
	_CleanupTemp
		Removes any temp files leftover from a previous execution.
	
************************************************************ PETED ***********/
void APP::_CleanupTemp(void)
{
	FNI fni;

	/* Attempt to cleanup any leftovers from a previous bad exit */
	vftgTemp = kftgSocTemp;
	if (fni.FGetTemp())
		{
		FTG ftgTmp = vftgTemp;
		FNE fne;

		if (fne.FInit(&fni, &ftgTmp, 1))
			{
			bool fFlushErs = fFalse;

			while (fne.FNextFni(&fni))
				{
				if (!fni.FDelete())
					fFlushErs = fTrue;
				}

			if (fFlushErs)
				vpers->Flush(ercFniDelete);
			}
		}
}


/***************************************************************************
	Determine if another copy of 3DMM is already running by trying to
	create a named semaphore.
***************************************************************************/
bool APP::_FAppAlreadyRunning(void)
{
	AssertBaseThis(0);
	
#ifdef WIN
	HANDLE hsem;
	STN stn;
	
	GetStnAppName(&stn);

	hsem = CreateSemaphore(NULL, 0, 1, stn.Psz());
	if (hsem != NULL && GetLastError() == ERROR_ALREADY_EXISTS)
		{
		CloseHandle(hsem);
		return fTrue;
		}
#endif //WIN
#ifdef MAC
	RawRtn();
#endif //MAC
	return fFalse;
}


/***************************************************************************
	Try to find another instance of 3DMM and bring its window to front
	Also, if a document was on the command line, notify the other instance
***************************************************************************/
void APP::_TryToActivateWindow(void)
{
	AssertBaseThis(0);
	
#ifdef WIN
	HWND hwnd;
	STN stn;
	FNI fniUserDoc;
	
	GetStnAppName(&stn);	
	hwnd = FindWindow(kpszAppWndCls, stn.Psz());
	if (NULL != hwnd)
		{
		SetForegroundWindow(hwnd);
		ShowWindow(hwnd, SW_RESTORE); // in case it was minimized
		_ParseCommandLine();
		GetPortfolioDoc(&fniUserDoc);
		if (fniUserDoc.Ftg() != ftgNil)
			_FSendOpenDocCmd(hwnd, &fniUserDoc); // ignore failure
		}
#endif //WIN
#ifdef MAC
	RawRtn();
#endif //MAC
}


/***************************************************************************
	If we're not running on at least Win95 or NT 3.51, complain and return
	fFalse.
***************************************************************************/
bool APP::_FEnsureOS(void)
{
	AssertBaseThis(0);
#ifdef WIN
	DWORD dwVersion;
	byte bVersionMajor;
	byte bVersionMinor;
	PDLG pdlg;
	
	dwVersion = GetVersion();
	bVersionMinor = (byte)((dwVersion & 0x0000ff00) >> 8);
	bVersionMajor = (byte)(dwVersion & 0x000000ff);

	if (bVersionMajor >= 4 || (bVersionMajor == 3 && bVersionMinor >= 51))
		return fTrue;

	// Put up an alert: OS too old
	pdlg = DLG::PdlgNew(dlidBadOS, pvNil, pvNil);
	if (pvNil == pdlg)
		return fFalse;
	pdlg->IditDo();
	ReleasePpo(&pdlg);
	_fDontReportInitFailure = fTrue;
#endif //WIN
#ifdef MAC
	RawRtn();
#endif //MAC
	return fFalse; // Bad OS
}

	
/***************************************************************************
	Notifies the user if there is no suitable wave-out and/or midi-out
	hardware.
***************************************************************************/
bool APP::_FEnsureAudio(void)
{
	AssertBaseThis(0);

#ifdef WIN
	long cwod; // count of wave-out devices
	long cmod; // count of midi-out devices
	bool fShowMessage;
	PDLG pdlg;


	cwod = waveOutGetNumDevs();
	if (cwod <= 0)
		{
		fShowMessage = fTrue;
		if (!FGetSetRegKey(kszWaveOutMsgValue, &fShowMessage,
				size(fShowMessage), fregSetDefault | fregMachine))
			{
			Warn("Registry query failed");
			}
		if (fShowMessage)
			{
			// Put up an alert: no waveout
			pdlg = DLG::PdlgNew(dlidNoWaveOut, pvNil, pvNil);
			if (pvNil == pdlg)
				return fFalse;
			pdlg->IditDo();
			fShowMessage = !pdlg->FGetCheck(1); // 1 is ID of checkbox
			ReleasePpo(&pdlg);
			if (!fShowMessage)
				{
				// ignore failure
				FGetSetRegKey(kszWaveOutMsgValue, &fShowMessage,
					size(fShowMessage), fregSetKey | fregMachine);
				}
			}
		}
	if (HWD_SUCCESS == wHaveWaveDevice(WAVE_FORMAT_2M08)) // 22kHz, Mono, 8bit is our minimum
		{
		if(wHaveACM())
			{
			// audio compression manager (sound mapper) not installed
			wInstallComp(IC_ACM);
			_fDontReportInitFailure = fTrue;
			return fFalse;
			}

		if(wHaveACMCodec(WAVE_FORMAT_ADPCM))
			{
			// audio codecs not installed
			wInstallComp(IC_ACM_ADPCM);
			_fDontReportInitFailure = fTrue;
			return fFalse;
			}
		} // have wave device

	cmod = midiOutGetNumDevs();
	if (cmod <= 0)
		{
		fShowMessage = fTrue;
		if (!FGetSetRegKey(kszMidiOutMsgValue, &fShowMessage,
				size(fShowMessage), fregSetDefault | fregMachine))
			{
			Warn("Registry query failed");
			}
		if (fShowMessage)
			{
			// Put up an alert: no midiout
			pdlg = DLG::PdlgNew(dlidNoMidiOut, pvNil, pvNil);
			if (pvNil == pdlg)
				return fFalse;
			pdlg->IditDo();
			fShowMessage = !pdlg->FGetCheck(1); // 1 is ID of checkbox
			ReleasePpo(&pdlg);
			if (!fShowMessage)
				{
				// ignore failure
				FGetSetRegKey(kszMidiOutMsgValue, &fShowMessage,
					size(fShowMessage), fregSetKey | fregMachine);
				}
			}
		}
#endif //WIN
#ifdef MAC
	RawRtn();
#endif //MAC
	return fTrue;
}

	
/***************************************************************************
	Notifies the user if there is no suitable video playback devices
***************************************************************************/
bool APP::_FEnsureVideo(void)
{
	if(wHaveMCI("AVIVIDEO"))
		{
		// MCI for video is not installed
		wInstallComp(IC_MCI_VFW);
		_fDontReportInitFailure = fTrue;
		return fFalse;
		}

	if(HIC_SUCCESS != wHaveICMCodec(MS_VIDEO1))
		{
		//  video 1 codec not installed
		wInstallComp(IC_ICM_VIDEO1);
		_fDontReportInitFailure = fTrue;
		return fFalse;
		}

	return fTrue;
}
	
/***************************************************************************
	Returns fTrue if color depth is >= 8 bits per pixel.  User is alerted
	if depth is less than 8, and function fails.  User is warned if depth
	is greater than 8, unless he has clicked the "don't show this messsage
	again" in the dialog before.
***************************************************************************/
bool APP::_FEnsureColorDepth(void)
{
	AssertBaseThis(0);

#ifdef WIN
	HDC hdc;
	long cbitPixel;
	PDLG pdlg;
	bool fShowMessage;
	bool fDontShowAgain = fFalse;
	
	hdc = GetDC(NULL);
	if (NULL == hdc)
		return fFalse;
	cbitPixel = GetDeviceCaps(hdc, BITSPIXEL);
	ReleaseDC(NULL, hdc);

	if (cbitPixel < 8)
		{
		// Put up an alert: Not enough colors
		pdlg = DLG::PdlgNew(dlidNotEnoughColors, pvNil, pvNil);
		if (pvNil == pdlg)
			return fFalse;
		pdlg->IditDo();
		ReleasePpo(&pdlg);
		_fDontReportInitFailure = fTrue;
		return fFalse;
		}
	if (cbitPixel > 8)
		{
		// warn user
		fShowMessage = fTrue;
		if (!FGetSetRegKey(kszGreaterThan8bppMsgValue, &fShowMessage,
				size(fShowMessage), fregSetDefault))
			{
			Warn("Registry query failed");
			}
		if (fShowMessage)
			{
			pdlg = DLG::PdlgNew(dlidTooManyColors, pvNil, pvNil);
			if (pvNil != pdlg)
				{
				pdlg->IditDo();
				fDontShowAgain = pdlg->FGetCheck(1); // 1 is ID of checkbox
				}
			ReleasePpo(&pdlg);
			if (fDontShowAgain)
				{
				fShowMessage = fFalse;
				FGetSetRegKey(kszGreaterThan8bppMsgValue, &fShowMessage,
					size(fShowMessage), fregSetKey); // ignore failure
				}
			}
		}
#endif //WIN
#ifdef MAC
	RawRtn();
#endif //MAC
	return fTrue;
}


/***************************************************************************
	Dialog proc for Resolution Switch dialog.  Return fTrue (bring down
	the dialog) if kdtsMaxResSwitchDlg has passed since the dialog was
	created.
***************************************************************************/
bool _FDlgResSwitch(PDLG pdlg, long *pidit, void *pv)
{
	AssertPo(pdlg, 0);
	AssertVarMem(pidit);
	AssertPvCb(pv, size(long));

	long tsResize = *(long *)pv;
	long tsCur;

	if (*pidit != ivNil)
		{
		return fTrue; // Cancel or OK pressed
		}
	else
		{
		tsCur = TsCurrent();
		if (tsCur - tsResize >= kdtsMaxResSwitchDlg)
			return fTrue;
		}

	return fFalse;
}


/***************************************************************************
	Ensure that the screen is at the user's preferred resolution for 3DMM.
	If user has no registry preference, we offer to switch, try to switch,
	and save user's	preference.  Registry failures are non-fatal, but
	failing to create the main window causes this function to fail.

	When this function returns, _fRunInWindow is set correctly and the
	main app window *might* be created.
***************************************************************************/
bool APP::_FEnsureDisplayResolution(void)
{
	AssertBaseThis(0);

	PDLG pdlg;
	long idit;
	bool fSwitchRes;
	bool fNoValue;
	long tsResize;

	if (_FDisplayIs640480())
		{
		// System is already 640x480, so ignore registry and run fullscreen
		_fRunInWindow = fFalse;
		return fTrue;
		}

	if (!_FDisplaySwitchSupported())
		{
		// System can't switch res, so ignore registry and run in a window
		_fRunInWindow = fTrue;
		return fTrue;
		}

	// See if there's a res switch preference in the registry
	if (!FGetSetRegKey(kszSwitchResolutionValue, &fSwitchRes,
			size(fSwitchRes), fregNil, &fNoValue))
		{
		// Registry error...just run in a window
		_fRunInWindow = fTrue;
		return fTrue;
		}

	if (!fNoValue)
		{
		// User has a preference
		if (!fSwitchRes)
			{
			_fRunInWindow = fTrue;
			return fTrue;
			}
		else // try to switch res
			{
			_fRunInWindow = fFalse;
			if (!_FInitOS())
				{
				// we're screwed
				return fFalse;
				}
			if (!_FSwitch640480(fTrue))
				{
				_fRunInWindow = fTrue;
				_RebuildMainWindow();
				goto LSwitchFailed;
				}
			return fTrue;
			}
		}

	// User doesn't have a preference yet.  Do the interactive thing.
#ifdef RES_SWITCH_DIALOGS
	pdlg = DLG::PdlgNew(dlidDesktopResizing, pvNil, pvNil);
	if (pvNil == pdlg)
		return fFalse;
	idit = pdlg->IditDo();
	ReleasePpo(&pdlg);
#else //!RES_SWITCH_DIALOGS
	idit = 2; // OK
#endif //!RES_SWITCH_DIALOGS
	if (idit == 1) // cancel
		{
		_fRunInWindow = fTrue;
		// try to set pref to fFalse
		fSwitchRes = fFalse;
		goto LWriteReg;
		}
	_fRunInWindow = fFalse;
	if (!_FInitOS())
		return fFalse;
	if (!_FSwitch640480(fTrue))
		{
		_fRunInWindow = fTrue;
		_RebuildMainWindow();
		goto LSwitchFailed;
		}

	tsResize = TsCurrent();
#ifdef RES_SWITCH_DIALOGS
	pdlg = DLG::PdlgNew(dlidDesktopResized, _FDlgResSwitch, &tsResize);
	idit = ivNil; // if dialog fails to come up, treat like a cancel
	if (pvNil != pdlg)
		idit = pdlg->IditDo();
	ReleasePpo(&pdlg);
#else //!RES_SWITCH_DIALOGS
	idit = 2; // OK
#endif //!RES_SWITCH_DIALOGS
	if (idit == 1 || idit == ivNil) // cancel or timeout or dialog failure
		{
		_fSwitchedResolution = fFalse;
		_fRunInWindow = fTrue;
		_RebuildMainWindow();
		if (!_FSwitch640480(fFalse)) // restore desktop resolution
			return fFalse;
		goto LSwitchFailed;
		}
	// try to set pref to fTrue
	fSwitchRes = fTrue;
	goto LWriteReg;

LSwitchFailed:
	pdlg = DLG::PdlgNew(dlidDesktopWontResize, pvNil, pvNil);
	if (pvNil != pdlg)
		pdlg->IditDo();
	ReleasePpo(&pdlg);
	// try to set pref to fFalse
	fSwitchRes = fFalse;
LWriteReg:
	FGetSetRegKey(kszSwitchResolutionValue, &fSwitchRes, size(fSwitchRes),
		fregSetKey);
	return fTrue;
}


/***************************************************************************
	Return whether display is currently 640x480
***************************************************************************/
bool APP::_FDisplayIs640480(void)
{
#ifdef WIN
	return (GetSystemMetrics(SM_CXSCREEN) == 640 &&
		GetSystemMetrics(SM_CYSCREEN) == 480);
#endif //WIN
#ifdef MAC
	RawRtn();
	return fFalse;
#endif //MAC
}


/***************************************************************************
	Do OS specific initialization.
***************************************************************************/
bool APP::_FInitOS(void)
{
	AssertBaseThis(0);

#ifdef WIN
	long dxpWindow;
	long dypWindow;
	long xpWindow;
	long ypWindow;
	DWORD dwStyle = 0;
	STN stnWindowTitle;

	if (_fMainWindowCreated) // If someone else called _FInitOS already,
		return fTrue;		 // we can leave

	if (!FGetStnApp(idsWindowTitle, &stnWindowTitle))
		return fFalse;

	// register the window classes
	if (vwig.hinstPrev == hNil)
		{
		WNDCLASS wcs;

		wcs.style = CS_BYTEALIGNCLIENT | CS_OWNDC;
		wcs.lpfnWndProc = _LuWndProc;
		wcs.cbClsExtra = 0;
		wcs.cbWndExtra = 0;
		wcs.hInstance = vwig.hinst;
		wcs.hIcon = LoadIcon(vwig.hinst, MAKEINTRESOURCE(IDI_APP));
		wcs.hCursor = LoadCursor(NULL, IDC_ARROW);
		wcs.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
		wcs.lpszMenuName = 0;
		wcs.lpszClassName = kpszAppWndCls;
		if (!RegisterClass(&wcs))
			return fFalse;

		wcs.lpfnWndProc = _LuMdiWndProc;
		wcs.lpszClassName = PszLit("MDI");
		if (!RegisterClass(&wcs))
			return fFalse;
		}

	_GetWindowProps(&xpWindow, &ypWindow, &dxpWindow, &dypWindow, &dwStyle);

	if ((vwig.hwndApp = CreateWindow(kpszAppWndCls, stnWindowTitle.Psz(),
		dwStyle, xpWindow, ypWindow, dxpWindow, dypWindow,
		hNil, hNil, vwig.hinst, pvNil)) == hNil)
		{
		return fFalse;
		}

	if (hNil == (vwig.hdcApp = GetDC(vwig.hwndApp)))
		return fFalse;

	//set a timer, so we can idle regularly.
	if (SetTimer(vwig.hwndApp, 0, 1, pvNil) == 0)
		return fFalse;

	_haccel = LoadAccelerators(vwig.hinst, MIR(acidMain));
	_haccelGlobal = LoadAccelerators(vwig.hinst, MIR(acidGlobal));
	vwig.haccel = _haccel;

	ShowWindow(vwig.hwndApp, vwig.wShow);
	_fMainWindowCreated = fTrue;
#endif //WIN
#ifdef MAC
	RawRtn();
#endif //MAC
	return fTrue;
}


/***************************************************************************
	Determine window size, position, and window style.  Note that you
	should pass in the current dwStyle if the window already exists.  If
	the window does not already exist, pass in 0.
***************************************************************************/
void APP::_GetWindowProps(long *pxp, long *pyp, long *pdxp, long *pdyp,
	DWORD *pdwStyle)
{
	AssertBaseThis(0);
	AssertVarMem(pxp);
	AssertVarMem(pyp);
	AssertVarMem(pdxp);
	AssertVarMem(pdyp);
	AssertVarMem(pdwStyle);

	if (!_fRunInWindow)
		{
		*pdwStyle |= (WS_POPUP | WS_CLIPCHILDREN);
		*pdwStyle &= ~(WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU |
			WS_MINIMIZEBOX);
		*pxp = 0;
		*pyp = 0;
		*pdxp = GetSystemMetrics(SM_CXSCREEN);
		*pdyp = GetSystemMetrics(SM_CYSCREEN);
		}
	else
		{
		RCS rcs;
		*pdwStyle |= (WS_OVERLAPPED | WS_CLIPCHILDREN | WS_CAPTION | WS_SYSMENU |
			WS_MINIMIZEBOX);
		*pdwStyle &= ~WS_POPUP;
		rcs.left = 0;
		rcs.top = 0;
		rcs.right = 640;
		rcs.bottom = 480;
		AdjustWindowRect(&rcs, *pdwStyle, fFalse);
		*pdxp = rcs.right - rcs.left;
		*pdyp = rcs.bottom - rcs.top;
		// Center window on screen
		// REVIEW *****: how do you adjust window appropriately for taskbar?
		*pxp = LwMax((GetSystemMetrics(SM_CXSCREEN) - *pdxp) / 2, 0);
		*pyp = LwMax((GetSystemMetrics(SM_CYSCREEN) - *pdyp) / 2, 0);
		}
}


/***************************************************************************
	Change main window properties based on whether we're running in a
	window or fullscreen.
***************************************************************************/
void APP::_RebuildMainWindow(void)
{
	AssertBaseThis(0);
	Assert(_fMainWindowCreated, 0);

#ifdef WIN
	long dxpWindow;
	long dypWindow;
	long xpWindow;
	long ypWindow;
	DWORD dwStyle;

	dwStyle = GetWindowLong(vwig.hwndApp, GWL_STYLE);
	_GetWindowProps(&xpWindow, &ypWindow, &dxpWindow, &dypWindow, &dwStyle);
	SetWindowLong(vwig.hwndApp, GWL_STYLE, dwStyle);
	SetWindowPos(vwig.hwndApp, HWND_TOP, xpWindow, ypWindow, dxpWindow,
		dypWindow, 0);
#endif //WIN
#ifdef MAC
	RawRtn();
#endif //MAC
}


/***************************************************************************
	Open "3D Movie Maker.chk" or "3DMovie.chk"
***************************************************************************/
bool APP::_FOpenResourceFile(void)
{
	AssertBaseThis(0);
	AssertPo(&_fniProductDir, ffniDir);

	FNI fni;
	STN stn;

	fni = _fniProductDir;
	if (!fni.FSetLeaf(&_stnProductLong, kftgChunky) || tYes != fni.TExists())
		{
		fni = _fniProductDir;
		if (!fni.FSetLeaf(&_stnProductShort, kftgChunky) ||
			tYes != fni.TExists())
			{
			fni.GetStnPath(&stn);
	 		_FCantFindFileDialog(&stn); // ignore failure
			return fFalse;
			}
		}
	_pcfl = CFL::PcflOpen(&fni, fcflNil);
	if (pvNil == _pcfl)
		return fFalse;
	return fTrue;
}


/***************************************************************************
	Report that 3DMM can't find a file named pstnFile
***************************************************************************/
bool APP::_FCantFindFileDialog(PSTN pstnFile)
{
	AssertBaseThis(0);
	AssertPo(pstnFile, 0);

	PDLG pdlg;

	pdlg = DLG::PdlgNew(dlidCantFindFile, pvNil, pvNil);
	if (pvNil == pdlg)
		return fFalse;

	if (!pdlg->FPutStn(1, pstnFile))
		{
		ReleasePpo(&pdlg);
		return fFalse;
		}
	pdlg->IditDo();
	ReleasePpo(&pdlg);
	_fDontReportInitFailure = fTrue;
	return fTrue;
}


/******************************************************************************
	Finds _fniUsersDir, _fniMelanieDir, and _fniUserDir.  _fniUserDir is
	set from the registry.  This function also determines if this is a first
	time user.
************************************************************ PETED ***********/
bool APP::_FGetUserDirectories(void)
{
	AssertBaseThis(0);
	AssertPo(&_fniMsKidsDir, ffniDir);
	Assert(_stnUser.Cch() > 0, "need valid stnUser!");

	SZ szDir;
	STN stn;
	STN stnT;
	STN stnUsers;
	bool fFirstTimeUser;

	// First, find the Users directory
	_fniUsersDir = _fniMsKidsDir;
	if (!FGetStnApp(idsUsersDir, &stnUsers))
		return fFalse;
	if (!_fniUsersDir.FDownDir(&stnUsers, ffniMoveToDir))
		{
		_fniUsersDir.GetStnPath(&stn);
		if (!stn.FAppendStn(&stnUsers))
			return fFalse;
		_FCantFindFileDialog(&stn); // ignore failure
		return fFalse;
		}
	AssertPo(&_fniUsersDir, ffniDir);

	// Find Melanie's dir
	_fniMelanieDir = _fniUsersDir;
	if (!FGetStnApp(idsMelanie, &stn))
		return fFalse;
	if (!_fniMelanieDir.FDownDir(&stn, ffniMoveToDir))
		{
		_fniMelanieDir.GetStnPath(&stnT);
		if (!stnT.FAppendStn(&stn))
			return fFalse;
		_FCantFindFileDialog(&stnT); // ignore failure
		return fFalse;
		}
	AssertPo(&_fniMelanieDir, ffniDir);

	fFirstTimeUser = fFalse;
	szDir[0] = chNil;
	if (!FGetSetRegKey(kszHomeDirValue, szDir, size(szDir),
			fregSetDefault | fregString))
		{
		return fFalse;
		}
	stn.SetSz(szDir);
	if (stn.Cch() == 0 || !_fniUserDir.FBuildFromPath(&stn, kftgDir) ||
			tYes != _fniUserDir.TExists())
		{
		// Need to (find or create) and go to user's directory
		fFirstTimeUser = fTrue;
		_fniUserDir = _fniUsersDir;

		// Ensure that user's root directory exists
		if (!_fniUserDir.FDownDir(&_stnUser, ffniMoveToDir))
			{
			if (!_fniUserDir.FDownDir(&_stnUser, ffniCreateDir | ffniMoveToDir))
				return fFalse;
			}

		// Try to write path to user dir to the registry
		_fniUserDir.GetStnPath(&stn);
		stn.GetSz(szDir);
		FGetSetRegKey(kszHomeDirValue, szDir, CchSz(szDir) + kcchExtraSz,
			fregSetKey | fregString); // ignore failure
		}
#ifdef WIN
	if (SetCurrentDirectory(szDir) == FALSE)
		return fFalse;
#else //!WIN
	RawRtn();
#endif //!WIN
	AssertPo(&_fniUserDir, ffniDir);

	if (!FSetProp(kpridFirstTimeUser, fFirstTimeUser))
		return fFalse;
	return fTrue;
}


/******************************************************************************
	_FGetUserName
		Attempts to get the current user name.  May return a default value
		if we get a "non-serious" error (as defined in the spec).
	
	Arguments:
		None
	
	Returns:  fTrue if _stnUser has something usable in it on return
	
************************************************************ PETED ***********/
bool APP::_FGetUserName(void)
{
	AssertBaseThis(0);

#ifdef WIN
	bool fRet = fTrue;
	DWORD dwCbUser = size(SZ);
	SZ szT;

	switch (WNetGetUser(NULL, szT, &dwCbUser))
		{
	case ERROR_EXTENDED_ERROR:
		{
		DWORD dwError;
		SZ szProvider;
		STN stnMessage;

		if (WNetGetLastError(&dwError, szT, size(SZ), szProvider, size(SZ))
				== NO_ERROR)
			{
			STN stnFormat;

			if (FGetStnApp(idsWNetError, &stnFormat))
				{
				stnMessage.FFormat(&stnFormat, szProvider, dwError, szT);
				TGiveAlertSz(stnMessage.Psz(), bkOk, cokExclamation);
				}
			}
		else
			Bug("Call to WNetGetLastError failed; this should never happen");
		_stnUser.SetNil();
		fRet = fFalse;
		break;
		}

	case NO_ERROR:
		_stnUser = szT;
		if (_stnUser.Cch() > 0)
			break; // else fall through...

	case ERROR_MORE_DATA:
	case ERROR_NO_NETWORK:
	case ERROR_NO_NET_OR_BAD_PATH:
	default:
		if (!FGetStnApp(idsDefaultUser, &_stnUser))	
			fRet = fFalse;
		break;
		}

	Assert(!fRet || _stnUser.Cch() > 0, "Bug in _FGetUserName");
	return fRet;
#else // WIN
	RawRtn();
	return fFalse;
#endif // !WIN
}


/******************************
	The user-data structure
*******************************/
struct UDAT
	{
	long rglw[kcpridUserData];
	};

/***************************************************************************
	Reads the "user data" from the registry and SetProp's the data onto
	the app
***************************************************************************/
bool APP::_FReadUserData(void)
{
	AssertBaseThis(0);

	UDAT udat;
	long iprid;

	ClearPb(&udat, size(UDAT));

	if (!FGetSetRegKey(kszUserDataValue, &udat, size(UDAT),
		fregSetDefault | fregBinary))
		{
		return fFalse;
		}
	for (iprid = 0; iprid < kcpridUserData; iprid++)
		{
		if (!FSetProp(kpridUserDataBase + iprid, udat.rglw[iprid]))
			return fFalse;
		}

	return fTrue;
}


/***************************************************************************
	Writes the "user data" from the app props to the registry
***************************************************************************/
bool APP::_FWriteUserData(void)
{
	AssertBaseThis(0);

	UDAT udat;
	long iprid;

	for (iprid = 0; iprid < kcpridUserData; iprid++)
		{
		if (!FGetProp(kpridUserDataBase + iprid, &udat.rglw[iprid]))
			return fFalse;
		}
	if (!FGetSetRegKey(kszUserDataValue, &udat, size(UDAT),
		fregSetKey | fregBinary))
		{
		return fFalse;
		}

	return fTrue;
}


/******************************************************************************
	FGetSetRegKey
		Given a reg key (and option sub-key), attempts to either get or set
		the current value of the key.  If the reg key is created on a Get
		(fSetKey == fFalse), the data in pvData will be used to set the
		default value for the key.
	
	
	Arguments:
		PSZ pszValueName  --  The value name
		void *pvData      --  pointer to buffer to read or write key into or from
		long cbData       --  size of the buffer
		ulong grfreg      --  flags describing what we should do
		bool *pfNoValue  --  optional parameter, takes whether a real registry
		                      error occurred or not
	
	Returns:  fTrue if all actions necessary could be performed
	
************************************************************ PETED ***********/
bool APP::FGetSetRegKey(PSZ pszValueName, void *pvData, long cbData,
	ulong grfreg, bool *pfNoValue)
{
	AssertBaseThis(0);
	AssertSz(pszValueName);
	AssertPvCb(pvData, cbData);
	AssertNilOrVarMem(pfNoValue);

	bool fRet = fFalse;
	bool fSetKey, fSetDefault, fString, fBinary;
	
	fSetKey = grfreg & fregSetKey;
	fSetDefault = grfreg & fregSetDefault;
	fString = grfreg & fregString;
	fBinary = grfreg & fregBinary;

#ifdef WIN
	DWORD dwDisposition;
	DWORD dwCbData = cbData;
	DWORD dwType;
	HKEY hkey = 0;

	if (RegCreateKeyEx((grfreg & fregMachine) ? HKEY_LOCAL_MACHINE :
			HKEY_CURRENT_USER, kszSocratesKey, 0, NULL, REG_OPTION_NON_VOLATILE,
			KEY_ALL_ACCESS, NULL, &hkey, &dwDisposition) != ERROR_SUCCESS)
		{
		goto LFail;
		}

	if ((dwDisposition == REG_CREATED_NEW_KEY && fSetDefault) || fSetKey)
		{
LWriteValue:
		if (fBinary)
			{
			dwType = REG_BINARY;
			}
		else if (fString)
			{
			if (!fSetKey)
				dwCbData = CchSz((PSZ)pvData) + kcchExtraSz;
			else
				Assert(CchSz((PSZ)pvData) < cbData, "Invalid string for reg key");
			dwType = REG_SZ;
			}
		else
			{
			Assert(cbData == size(DWORD), "Unknown reg key type");
			dwType = REG_DWORD;
			}

		if (RegSetValueEx(hkey, pszValueName, NULL, dwType, (byte *)pvData,
			dwCbData) != ERROR_SUCCESS)
			{
			goto LFail;
			}
		}
	else
		{
		long lwRet;

		if ((lwRet = RegQueryValueEx(hkey, pszValueName, NULL, &dwType,
				(byte *)pvData, &dwCbData)) != ERROR_SUCCESS)
			{
			if (lwRet == ERROR_FILE_NOT_FOUND && fSetDefault)
				goto LWriteValue;
			/* If the caller gave us a way to differentiate a genuine registry
				failure from simply not having set the value yet, do so */
			if (pfNoValue != pvNil)
				fRet = *pfNoValue = (lwRet == ERROR_FILE_NOT_FOUND);
			goto LFail;
			}
		Assert(dwType == (DWORD)(fString ? REG_SZ : (fBinary ? REG_BINARY : REG_DWORD)), "Invalid key type");
		}
	if (pfNoValue != pvNil)
		*pfNoValue = fFalse;
	fRet = fTrue;
LFail:
	if (hkey != 0)
		RegCloseKey(hkey);
#else // WIN
	RawRtn();
#endif // !WIN
	return fRet;
}


/***************************************************************************
	Set the palette and bring up the Microsoft Home Logo
***************************************************************************/
bool APP::_FDisplayHomeLogo(void)
{
	AssertBaseThis(0);
	AssertPo(_pcfl, 0);

	BLCK blck;
	PGL pglclr;
	PMBMP pmbmp;
	short bo;
	short osk;

	if (!_pcfl->FFind(kctgColorTable, kcnoGlcrInit, &blck))
		return fFalse;
	pglclr = GL::PglRead(&blck, &bo, &osk);
	if (pvNil == pglclr)
		return fFalse;
	GPT::SetActiveColors(pglclr, fpalIdentity);
	ReleasePpo(&pglclr);

	if (!_pcfl->FFind(kctgMbmp, kcnoMbmpHomeLogo, &blck))
		return fFalse;
	pmbmp = MBMP::PmbmpRead(&blck);
	if (pvNil == pmbmp)
		return fFalse;
	_pkwa->SetMbmp(pmbmp);
	ReleasePpo(&pmbmp);
	UpdateMarked();
	return fTrue;
}


/***************************************************************************
	Initialize tag manager
***************************************************************************/
bool APP::_FInitProductNames(void)
{
	AssertBaseThis(0);

	PGST pgst;
	BLCK blck;

	// Use kcbCacheTagm of cache per source, don't cache on CD
    vptagm = TAGM::PtagmNew(&_fniMsKidsDir, APP::FInsertCD, kcbCacheTagm);
 	if (vptagm == pvNil)
		return fFalse;

	if (!_FReadTitlesFromReg(&pgst))
		goto LFail;

	if (!vptagm->FMergeGstSource(pgst, kboCur, koskCur))
		goto LFail;

	if (!_FFindProductDir(pgst))
		goto LFail;

	if (!vptagm->FGetSid(&_stnProductLong, &_sidProduct))
		goto LFail;

	ReleasePpo(&pgst);
	return fTrue;

LFail:
	ReleasePpo(&pgst);
	return fFalse;
}


/***************************************************************************
	Read the sids and titles of installed 3DMovie products from the registry
***************************************************************************/
bool APP::_FReadTitlesFromReg(PGST *ppgst)
{
	AssertBaseThis(0);
	AssertVarMem(ppgst);

#ifdef WIN
	HKEY hkey = 0;
	DWORD dwDisposition;
	DWORD iValue;
	SZ szSid;
	STN stnSid;
	DWORD cchSid = kcchMaxSz;
	SZ szTitle;
	STN stnTitle;
	DWORD cchTitle = kcchMaxSz;
	PGST pgst;
	long sid;

	if ((pgst = GST::PgstNew(size(long))) == pvNil)
		goto LFail;
	if (RegCreateKeyEx(HKEY_LOCAL_MACHINE, kszProductsKey, 0, NULL, REG_OPTION_NON_VOLATILE,
			KEY_ALL_ACCESS, NULL, &hkey, &dwDisposition) != ERROR_SUCCESS)
		{
		Warn("Missing InstallDirectory registry entry or registry error");
		goto LFail;
		}
	
	for (iValue = 0; RegEnumValue(hkey, iValue, szSid, &cchSid, NULL, 
		NULL, (unsigned char *)szTitle, &cchTitle) != ERROR_NO_MORE_ITEMS; iValue++) 
		{
		stnSid.SetSz(szSid);
		if (!stnSid.FGetLw(&sid))
			{
			Warn("Invalid registry name for Products key value");
			continue;
			}
		stnTitle.SetSz(&szTitle[0]); 	
		if (!pgst->FAddStn(&stnTitle, &sid))
			goto LFail;
		cchTitle = kcchMaxSz;
		cchSid = kcchMaxSz;
		}

#else  //!WIN
	RawRtn();
#endif //!WIN
	*ppgst = pgst;
	return fTrue;

LFail:
	ReleasePpo(&pgst);
	*ppgst = pvNil;
	return fFalse;
}

/***************************************************************************
	Initialize 3-D Text
***************************************************************************/
bool APP::_FInitTdt(void)
{
	AssertBaseThis(0);
	AssertPo(_pcfl, 0);

	PGST pgst;

	// set TDT action names
	pgst = _PgstRead(kcnoGstAction);
	if (pvNil == pgst)
		return fFalse;
	Assert(pgst->CbExtra() == size(long), "bad Action string table");
	if (!TDT::FSetActionNames(pgst))
		{
		ReleasePpo(&pgst);
		return fFalse;
		}
	ReleasePpo(&pgst);
	return fTrue;
}


/***************************************************************************
	Read and byte-swap a GST from _pcfl.  Assumes that extra data, if any,
	is a long.
***************************************************************************/
PGST APP::_PgstRead(CNO cno)
{
	AssertBaseThis(0);
	AssertPo(_pcfl, 0);

	PGST pgst;
	BLCK blck;
	short bo;
	short osk;
	long istn;
	long lwExtra;

	if (!_pcfl->FFind(kctgGst, cno, &blck))
		return pvNil;
	pgst = GST::PgstRead(&blck, &bo, &osk);
	if (pvNil == pgst)
		return pvNil;
	Assert(pgst->CbExtra() == 0 || pgst->CbExtra() == size(long),
		"unexpected extra size");
	if (kboCur != bo && 0 != pgst->CbExtra())
		{
		for (istn = 0; istn < pgst->IvMac(); istn++)
			{
			pgst->GetExtra(istn, &lwExtra);
			SwapBytesRglw(&lwExtra, 1);
			pgst->PutExtra(istn, &lwExtra);
			}
		}
	return pgst;
}


/***************************************************************************
	Read various string tables from _pcfl
***************************************************************************/
bool APP::_FReadStringTables(void)
{
	AssertBaseThis(0);
	AssertPo(_pcfl, 0);

	// read studio filename list
	_pgstStudioFiles = _PgstRead(kcnoGstStudioFiles);
	if (pvNil == _pgstStudioFiles)
		return fFalse;

	// read building filename list
	_pgstBuildingFiles = _PgstRead(kcnoGstBuildingFiles);
	if (pvNil == _pgstBuildingFiles)
		return fFalse;

	// read shared filename list
	_pgstSharedFiles = _PgstRead(kcnoGstSharedFiles);
	if (pvNil == _pgstSharedFiles)
		return fFalse;

	// read misc app strings
	_pgstApp = _PgstRead(kcnoGstApp);
	if (pvNil == _pgstApp)
		return fFalse;

	return fTrue;
}


/***************************************************************************
	Initialize KWA (app kidworld)
***************************************************************************/
bool APP::_FInitKidworld(void)
{
	AssertBaseThis(0);

	RC rcRel(0, 0, krelOne, krelOne);
	GCB gcb(CMH::HidUnique(), GOB::PgobScreen(), fgobNil, kginMark, pvNil,
		&rcRel);

	_pkwa = NewObj KWA(&gcb);
	if (pvNil == _pkwa)
		return fFalse;

	return fTrue;
}


/***************************************************************************
	Show the app splash screen
***************************************************************************/
bool APP::_FShowSplashScreen(void)
{
	AssertBaseThis(0);

	BLCK blck;
	PMBMP pmbmp;

	if (!_pcfl->FFind(kctgMbmp, kcnoMbmpSplash, &blck))
		return fFalse;
	pmbmp = MBMP::PmbmpRead(&blck);
	if (pvNil == pmbmp)
		return fFalse;
	_pkwa->SetMbmp(pmbmp);
	ReleasePpo(&pmbmp);
	UpdateMarked();
	return fTrue;
}


/***************************************************************************
	Play the app splash sound
***************************************************************************/
bool APP::_FPlaySplashSound(void)
{
	AssertBaseThis(0);

	PCRF pcrf;

	pcrf = CRF::PcrfNew(_pcfl, 0);
	if (pvNil == pcrf)
		return fFalse;
	vpsndm->SiiPlay(pcrf, kctgMidi, kcnoMidiSplash);
	ReleasePpo(&pcrf);

	return fTrue;
}


/***************************************************************************
	Read the chunky files specified by _pgstSharedFiles, _pgstBuildingFiles
	and _pgstStudioFiles and create the global CRM; indices to the Building
	and Studio CRFs are stored in _pglicrfBuilding and _pglicrfStudio.
***************************************************************************/
bool APP::_FInitCrm(void)
{
	AssertBaseThis(0);
	AssertPo(_pkwa, 0);
	AssertPo(_pgstSharedFiles, 0);
	AssertPo(_pgstBuildingFiles, 0);
	AssertPo(_pgstStudioFiles, 0);

	PSCEG psceg = pvNil;
	PSCPT pscpt = pvNil;

	_pcrmAll = CRM::PcrmNew(_pgstSharedFiles->IvMac() +
		_pgstBuildingFiles->IvMac() + _pgstStudioFiles->IvMac());
	if (pvNil == _pcrmAll)
		goto LFail;

	_pglicrfBuilding = GL::PglNew(size(long), _pgstBuildingFiles->IvMac());
	if (pvNil == _pglicrfBuilding)
		goto LFail;

	_pglicrfStudio = GL::PglNew(size(long), _pgstStudioFiles->IvMac());
	if (pvNil == _pglicrfStudio)
		goto LFail;

	if (!_FAddToCrm(_pgstSharedFiles, _pcrmAll, pvNil))
		goto LFail;

	if (!_FAddToCrm(_pgstBuildingFiles, _pcrmAll, _pglicrfBuilding))
		goto LFail;

	if (!_FAddToCrm(_pgstStudioFiles, _pcrmAll, _pglicrfStudio))
		goto LFail;

	// Initialize the shared util gob.
	psceg = _pkwa->PscegNew(_pcrmAll, _pkwa);
	if (pvNil == psceg)
		goto LFail;

	pscpt = (PSCPT)_pcrmAll->PbacoFetch(kctgScript, kcnoInitShared,
		SCPT::FReadScript);
	if (pvNil == pscpt)
		goto LFail;

	if (!psceg->FRunScript(pscpt))
		goto LFail;

	ReleasePpo(&psceg);
	ReleasePpo(&pscpt);
	return fTrue;
LFail:
	ReleasePpo(&psceg);
	ReleasePpo(&pscpt);
	return fFalse;
}


/***************************************************************************
	Helper function for _FInitCrm.  Adds the list of chunky files specified
	in pgstFiles to the CRM pointed to by pcrm.  If pglFiles is not pvNil,
	it is filled in with the positions in the CRM of each of the loaded
	crfs.
***************************************************************************/
bool APP::_FAddToCrm(PGST pgstFiles, PCRM pcrm, PGL pglFiles)
{
	AssertBaseThis(0);
	AssertPo(&_fniProductDir, ffniDir);
	AssertPo(pgstFiles, 0);
	AssertPo(pcrm, 0);
	AssertNilOrPo(pglFiles, 0);

	bool fRet = fFalse;
	FNI fni;
	STN stn;
	long istn;
	long cbCache;
	PCFL pcfl = pvNil;
	long icfl;

	for (istn = 0; istn < pgstFiles->IvMac(); istn++)
		{
		pgstFiles->GetStn(istn, &stn);
		pgstFiles->GetExtra(istn, &cbCache);
#ifdef DEBUG
		{
		bool fAskForCDSav = Pkwa()->FAskForCD();
		bool fFoundFile;

		// In debug, we look for "buildingd.chk", "sharedd.chk", etc. and
		// use them instead of the normal files if they exist.  If they
		// don't exist, just use the normal files.
		STN stnT = stn;
		stnT.FAppendCh(ChLit('d'));
		stnT.FAppendSz(PszLit(".chk")); // REVIEW *****
		Pkwa()->SetCDPrompt(fFalse);
		fFoundFile = Pkwa()->FFindFile(&stnT, &fni);
		Pkwa()->SetCDPrompt(fAskForCDSav);
		if (fFoundFile)
			{
			pcfl = CFL::PcflOpen(&fni, fcflNil);
			}
		else
			{
#endif //DEBUG
			stn.FAppendSz(PszLit(".chk")); // REVIEW *****
			if (Pkwa()->FFindFile(&stn, &fni))
				pcfl = CFL::PcflOpen(&fni, fcflNil);
#ifdef DEBUG
			}
		}
#endif //DEBUG
		if (pvNil == pcfl)
			{
			if (fni.Ftg() != ftgNil)
				fni.GetStnPath(&stn);
			if (!_fDontReportInitFailure)
				_FCantFindFileDialog(&stn); // ignore failure
			goto LFail;
			}
 		if (!pcrm->FAddCfl(pcfl, cbCache, &icfl))
			goto LFail;
		ReleasePpo(&pcfl);
		if (pglFiles != pvNil && !pglFiles->FAdd(&icfl))
			goto LFail;
		}

	fRet = fTrue;
LFail:
	if (!fRet)
		ReleasePpo(&pcfl);
	return fRet;

}


/***************************************************************************
	Initialize and start the building script
***************************************************************************/
bool APP::_FInitBuilding(void)
{
	AssertBaseThis(0);

 	bool fRet = fFalse;
	long i;
	long cbCache;
	long iv;
	PCRF pcrfT;
	PSCEG psceg = pvNil;
	PSCPT pscpt = pvNil;

	BeginLongOp();

	psceg = _pkwa->PscegNew(_pcrmAll, _pkwa);
	if (pvNil == psceg)
		goto LFail;

	pscpt = (PSCPT)_pcrmAll->PbacoFetch(kctgScript, kcnoStartApp,
		SCPT::FReadScript);
	if (pvNil == pscpt)
		goto LFail;

	if (!psceg->FRunScript(pscpt))
		goto LFail;

	// Up the cache limits of the building crfs in the global crm.
	// Assumption: since the files were added to the crm in the order they
	// appear in _pgstBuilding, I can get the cache amounts from there.
	for (i = 0; i < _pglicrfBuilding->IvMac(); i++)
		{
		_pgstBuildingFiles->GetExtra(i, &cbCache);
		_pglicrfBuilding->Get(i, &iv);
		pcrfT = _pcrmAll->PcrfGet(iv);
		Assert(pcrfT != pvNil, "Main CRM is corrupt.");
		pcrfT->SetCbMax(cbCache);
		}

	// Zero the cache for the studio crfs in the global crm.
	for (i = 0; i < _pglicrfStudio->IvMac(); i++)
		{
		_pglicrfStudio->Get(i, &iv);
		pcrfT = _pcrmAll->PcrfGet(iv);
		Assert(pcrfT != pvNil, "Main CRM is corrupt.");
		pcrfT->SetCbMax(0);
		}

	fRet = fTrue;
LFail:
	if (!fRet)
		EndLongOp();
	ReleasePpo(&psceg);
	ReleasePpo(&pscpt);
	return fRet;
}


/***************************************************************************
	Initialize and start the studio script
***************************************************************************/
bool APP::_FInitStudio(PFNI pfniUserDoc, bool fFailIfDocOpenFailed)
{
	AssertBaseThis(0);

	long i;
	long cbCache;
	long iv;
	PCRF pcrfT;
	bool fRet = fFalse;

	_pstdio = STDIO::PstdioNew(khidStudio, _pcrmAll,
		(pfniUserDoc->Ftg() == ftgNil ? pvNil : pfniUserDoc),
		fFailIfDocOpenFailed);
	if (_pstdio == pvNil)
		{
		goto LFail;
		}

	// Up the cache limits of the studio crfs in the global crm.
	for (i = 0; i < _pglicrfStudio->IvMac(); i++)
		{
		_pgstStudioFiles->GetExtra(i, &cbCache);
		_pglicrfStudio->Get(i, &iv);
		pcrfT = _pcrmAll->PcrfGet(iv);
		Assert(pcrfT != pvNil, "Main CRM is corrupt.");
		pcrfT->SetCbMax(cbCache);
		}

	// Zero the cache for the building crfs in the global crm.
	for (i = 0; i < _pglicrfBuilding->IvMac(); i++)
		{
		_pglicrfBuilding->Get(i, &iv);
		pcrfT = _pcrmAll->PcrfGet(iv);
		Assert(pcrfT != pvNil, "Main CRM is corrupt.");
		pcrfT->SetCbMax(0);
		}

	fRet = fTrue;

LFail:
	if (!fRet)
		PushErc(ercSocCantInitStudio);
	return fRet;
}


/***************************************************************************
	Sets _fSlowCPU if this is a slow CPU.  It tests the graphics, 
	fixed-point math, and memory copying speed, and if any of them are
	slower than a threshold, _fSlowCPU is set.
***************************************************************************/
bool APP::_FDetermineIfSlowCPU(void)
{
	AssertBaseThis(0);

	bool fSlowCPU;

	// If user has a saved preference, read and use that
	if (FGetSetRegKey(kszBetterSpeedValue, &fSlowCPU, size(bool), fregNil))
		{
		_fSlowCPU = fSlowCPU;
		return fTrue;
		}

	_fSlowCPU = fFalse;
	
#ifndef PERF_TEST

	return fTrue;

#else //PERF_TEST

	PGPT pgptWnd = pvNil;
	PGPT pgptOff = pvNil;
	RC rc1;
	RC rc2;
	ulong ts;
	ulong dts1;
	ulong dts2;
	ulong dts3;
	long i;
	char *pch1 = pvNil;
	char *pch2 = pvNil;
	BRS r1;
	BRS r2;
	BRS r3;

	// Test 1: Graphics.  Copy some graphics to an offscreen buffer,
	// then blit it back to the window 100 times.
	rc1.Set(0, 0, 300, 300);
	rc2.Set(0, 0, 300, 300);
	pgptWnd = GPT::PgptNewHwnd(vwig.hwndApp);
	if (pvNil == pgptWnd)
		goto LFail;
	pgptOff = GPT::PgptNewOffscreen(&rc1, 8); // BWLD RGB buffer is 8-bit
	if (pvNil == pgptOff)
		{
		goto LFail;
		}
	// BLOCK
		{
		GNV gnvWnd(pgptWnd);
		GNV gnvOff(pgptOff);
		gnvOff.CopyPixels(&gnvWnd, &rc1, &rc1);
		ts = TsCurrent();
		for (i = 0; i < 100; i++)
			gnvWnd.CopyPixels(&gnvOff, &rc1, &rc2);
		dts1 = TsCurrent() - ts;
		}
	ReleasePpo(&pgptWnd);
	ReleasePpo(&pgptOff);

	// Test 2: Math.  Do 200,000 fixed-point adds and multiplies
	r1 = BR_SCALAR(1.12); // arbitrary number
	r2 = BR_SCALAR(3.14159); // arbitrary number
	r3 = rZero;
	ts = TsCurrent();
	for (i = 0; i < 200000; i++)
		{
		r2 = BrsMul(r1, r2);
		r3 = BrsAdd(r2, r3);
		}
	dts2 = TsCurrent() - ts;

	// Test 3: Copying.  Copy a 50,000 byte block 200 times.
	if (!FAllocPv((void **)&pch1, 50000, mprNormal, fmemClear))
		goto LFail;
	if (!FAllocPv((void **)&pch2, 50000, mprNormal, fmemClear))
		goto LFail;
	ts = TsCurrent();
	for (i = 0; i < 200; i++)
		CopyPb(pch1, pch2, 50000);
	dts3 = TsCurrent() - ts;
	FreePpv((void **)&pch1);
	FreePpv((void **)&pch2);

	if (dts1 > 700 || dts2 > 200 || dts3 > 500)
		_fSlowCPU = fTrue;

	return fTrue;
LFail:
	ReleasePpo(&pgptWnd);
	ReleasePpo(&pgptOff);
	FreePpv((void **)&pch1);
	FreePpv((void **)&pch2);
	return fFalse;

#endif //PERF_TEST
}


/***************************************************************************
	Reads command line.  Also sets _fniExe to path to this executable
	and _fniPortfolioDoc to file specified on command line (if any).
	Also initializes _stnProductLong and _stnProductShort.

	-f: fast mode
	-s: slow mode
	-p"longname": long productname
	-t"shortname": short productname
	-m: don't minimize window on deactivate

***************************************************************************/
void APP::_ParseCommandLine(void)
{
	AssertBaseThis(0);

#ifdef WIN
	SZ sz;
	STN stn;
	achar *pch;
	achar *pchT;
	FNI fniT;

	// Get path to exe
	GetModuleFileName(NULL, sz, kcchMaxSz);
	stn.SetSz(sz);
	if(!_fniExe.FBuildFromPath(&stn))
		Bug("Bad module filename?");

	pch = vwig.pszCmdLine;
	// first argument (app name) is useless...skip it
	_SkipToSpace(&pch);
	_SkipSpace(&pch);

	while (*pch != chNil)
		{
		// Look for /options or -options
		if (*pch == ChLit('/') || *pch == ChLit('-'))
			{
			switch(ChUpper(*(pch + 1)))
				{
			case ChLit('M'):
				_fDontMinimize = fTrue;
				break;
			case ChLit('S'):
				_fSlowCPU = fTrue;
				break;
			case ChLit('F'):
				_fSlowCPU = fFalse;
				break;
			case ChLit('P'):
				{
				pch += 2; // skip "-p"
				pchT = pch;
				_SkipToSpace(&pchT);
				// skip quotes
				if (*pch == ChLit('"'))
					_stnProductLong.SetRgch(pch + 1, pchT - pch - 2);
				else
					_stnProductLong.SetRgch(pch, pchT - pch);
				}
				break;
			case ChLit('T'):
				{
				pch += 2; // skip "-t"
				pchT = pch;
				_SkipToSpace(&pchT);
				// skip quotes
				if (*pch == ChLit('"'))
					_stnProductShort.SetRgch(pch + 1, pchT - pch - 2);
				else
					_stnProductShort.SetRgch(pch, pchT - pch);
				}
				break;
			default:
				Warn("Bad command-line switch");
				break;
				}
			_SkipToSpace(&pch);
			_SkipSpace(&pch);
			}
		else // try to parse as fni string
			{
			// get to end of string
			pchT = pch + CchSz(pch);

			// skip quotes since FBuildFromPath can't deal
			if (*pch == ChLit('"'))
				pch++;

			// move pchT to begginning of file name itself
			while(	(*pchT != '\\') &&
					(pchT != pch))
				pchT--;
			// pchT now points to last'\', move it forward one (only if not pch)
			if (*pchT == '\\')
				pchT++;

			// set STN to the path, which is the string up to the last '\'
			stn.SetRgch(pch, pchT - pch);

			// try to map to long file name if we can
			HANDLE hFile = pvNil;
			WIN32_FIND_DATA W32FindData;

			hFile = FindFirstFile(pch, &W32FindData);
			if (INVALID_HANDLE_VALUE != hFile)
				{
				// append the longfile name returned...
				stn.FAppendSz(W32FindData.cFileName);
				FindClose(hFile);
				}
			else
				// just use what was passed orginally...
				stn.SetSz(pch);


			// remove ending quotes since FBuildFromPath can't deal
			if (stn.Psz()[stn.Cch() - 1] == ChLit('\"'))
				stn.Delete(stn.Cch() - 1, 1);
			if(fniT.FBuildFromPath(&stn))
				{
				SetPortfolioDoc(&fniT);
				}
			// move to end of string, we now assume that everything past options is the document name
			pch += CchSz(pch);
			}
		}
#endif //WIN
#ifdef MAC
	RawRtn();
#endif //MAC
}

/***************************************************************************
	Make sure that _stnProductLong and _stnProductShort are initialized to
	non-empty strings.  If _stnProductLong wasn't initialized from the
	command line, it and _stnProductShort are read from the app resource
	file.  If _stnProductLong is valid and _stnProductShort is not,
	_stnProductShort is just set to _stnProductLong.
***************************************************************************/
bool APP::_FEnsureProductNames(void)
{
	AssertBaseThis(0);
	
	if (_stnProductLong.Cch() == 0)
		{
#ifdef WIN
		SZ sz;

		if (0 == LoadString(vwig.hinst, stid3DMovieNameLong, sz, kcchMaxSz))
			return fFalse;
		_stnProductLong.SetSz(sz);

		if (0 == LoadString(vwig.hinst, stid3DMovieNameShort, sz, kcchMaxSz))
			return fFalse;
		_stnProductShort.SetSz(sz);
#else //MAC
		RawRtn();
#endif //MAC
		}

	if (_stnProductShort.Cch() == 0)
		_stnProductShort = _stnProductLong;

	return fTrue;
}


/***************************************************************************
	Find _fniMsKidsDir 
***************************************************************************/
bool APP::_FFindMsKidsDir(void)
{
	AssertBaseThis(0);
	Assert(_stnProductLong.Cch() > 0 && _stnProductShort.Cch() > 0,
		"_stnProductLong and _stnProductShort must exist");

	FNI fni;
	SZ szMsKidsDir;
	STN stn;
	STN stnUsers;

	szMsKidsDir[0] = chNil;
	if (!FGetSetRegKey(kszInstallDirValue, szMsKidsDir, size(SZ),
		fregMachine | fregString))
		{
		Warn("Missing InstallDirectory registry entry or registry error");
		}
	stn = szMsKidsDir;
	if (stn.Cch() == 0 || !_fniMsKidsDir.FBuildFromPath(&stn, kftgDir) ||
		tYes != _fniMsKidsDir.TExists())
		{
		// REVIEW *****: this artificial search is temp until we have a
		// real setup program and users have a InstallDirectory registry entry
		_fniMsKidsDir = _fniExe;
		if (!_fniMsKidsDir.FSetLeaf(pvNil, kftgDir))
			return fFalse;
		while (_fniMsKidsDir.FUpDir(pvNil, ffniMoveToDir))
			;
		/* REVIEW ***** (peted): if you check for the MSKIDS dir first, then
			you don't have to reset the dir string before presenting the error
			to the user */
		stn = PszLit("Microsoft Kids"); // REVIEW *****
		if (!_fniMsKidsDir.FDownDir(&stn, ffniMoveToDir))
			{
			stn = PszLit("MSKIDS"); // REVIEW *****
			if (!_fniMsKidsDir.FDownDir(&stn, ffniMoveToDir))
				{
				Warn("Can't find Microsoft Kids or MSKIDS.");
				stn = PszLit("Microsoft Kids");
				_FCantFindFileDialog(&stn); // ignore failure
				return fFalse;
				}
			}
		}

	AssertPo(&_fniMsKidsDir, ffniDir);
	return fTrue;
}

/***************************************************************************
	Find _fniProductDir 
	At this point, _stnProduct* is either the command line parameter or
	3D Movie Maker (in the absence of a command line parameter)
	_FFindProductDir() locates the .chk files by checking:
		first, _stnProduct* directories, or 
		second, the registry of installed products.
	This routine updates _stnProductLong and _stnProductShort on return.
***************************************************************************/
bool APP::_FFindProductDir(PGST pgst)
{
	AssertBaseThis(0);
	AssertVarMem(pgst);

	STN stnLong;
	STN stnShort;
	STN stn;
	FNI fni;
	long istn;

	if (_FQueryProductExists(&_stnProductLong, &_stnProductShort, &_fniProductDir))
		return fTrue;

	for (istn = 0; istn < pgst->IstnMac(); istn++)
		{
		pgst->GetStn(istn, &stn);
		vptagm->SplitString(&stn, &stnLong, &stnShort);
		if (_FQueryProductExists(&stnLong, &stnShort, &fni))
			{
			_stnProductLong = stnLong;
			_stnProductShort = stnShort;
			_fniProductDir = fni;
			return fTrue;
			}
		}
	return fFalse;
}

/***************************************************************************
	See if the product exists.
	Method:  See if the directory and chunk file exist.
***************************************************************************/
bool APP::_FQueryProductExists(STN *pstnLong, STN *pstnShort, FNI *pfni)
{
	AssertBaseThis(0);
	AssertVarMem(pfni);
	AssertPo(pstnLong, 0);
	AssertPo(pstnShort, 0);

	FNI fni;
	STN stn;

	*pfni = _fniMsKidsDir;
	if (!pfni->FDownDir(pstnLong, ffniMoveToDir) &&
		!pfni->FDownDir(pstnShort, ffniMoveToDir))
		{
		pfni->GetStnPath(&stn);
		if (!stn.FAppendStn(&_stnProductLong))
			goto LFail;
		_FCantFindFileDialog(&stn); // ignore failure
		goto LFail;
		}

	fni = *pfni;
	if (fni.FSetLeaf(pstnLong, kftgChunky) && (tYes == fni.TExists()))
		return fTrue;
	fni = *pfni;
	if (fni.FSetLeaf(pstnShort, kftgChunky) && (tYes == fni.TExists()))
		return fTrue;
LFail:
	TrashVar(pfni);
	return fFalse;
}

/***************************************************************************
	Advances *ppch until it points to either the next space character or
	the end of the string.  Exception: if the space character is surrounded
	by double quotes, this function skips by it.
***************************************************************************/
void APP::_SkipToSpace(char **ppch)
{
	AssertBaseThis(0);
	AssertVarMem(ppch);
	
	bool fQuote = fFalse;

	while(**ppch != chNil && (fQuote || **ppch != kchSpace))
		{
		if (**ppch == ChLit('"'))
			fQuote = !fQuote;
		(*ppch)++;
		}
}


/***************************************************************************
	Advances *ppch to the next non-space character.
***************************************************************************/
void APP::_SkipSpace(char **ppch)
{
	AssertBaseThis(0);
	AssertVarMem(ppch);
	
	while(**ppch == kchSpace)
		(*ppch)++;
}


/***************************************************************************
	Socrates window was activated or deactivated.
***************************************************************************/
void APP::_Activate(bool fActive)
{
	AssertBaseThis(0);

#ifdef WIN
	bool fIsIconic;

	APP_PAR::_Activate(fActive);

	fIsIconic = IsIconic(vwig.hwndApp);

	if (!fActive) // app was just deactivated
		{
		if (_FDisplayIs640480() && !_fDontMinimize && !fIsIconic)
			{
			// Note: using SW_MINIMIZE causes a bug where alt-tabbing
			// from this app to a fullscreen DOS window reactivates
			// this app.  So use SW_SHOWMINNOACTIVE instead.
			ShowWindow(vwig.hwndApp, SW_SHOWMINNOACTIVE); // minimize app
			_fMinimized = fTrue;
				
			// Note that we examine _fMinimized during the WM_DISPLAYCHANGE message
			// received as a result of the following res change call. Therefore the
			// minimize operation MUST precede the res switch.
			if (_fSwitchedResolution)
				_FSwitch640480(fFalse);

			// When the portfolio is displayed, the main app is automatically disabled.
			// This means all keyboard/mouse input directed at the main app window will
			// be ignored until the portfolio is finished with. If the app is minimized
			// here while the portfolio is displayed, then we will be left with a disabled
			// app window on the win95 task bar. As a result, the app will not appear
			// only the task window invoked by an Alt-tab, nor is it resized when
			// the user clicks on it in the taskbar, (even though win95 tries to
			// activate it). We could do the following...
			// (1) Do not auto-minimize the app window while the portfolio is displayed.
			//		This is what happens on NT.
			// (2) Drop the portfolio here, so the app window is enabled on the taskbar.
			// (3) Make sure the app window is enabled now, by doing this...
			EnableWindow(vwig.hwndApp, TRUE);

			// The concern with doing this, is that when the app is later restored,
			// it is then enabled when it shouldn't be, as the portfolio is still
			// up in front of it. As it happens, this doesn't matter because the
			// portfolio is full screen. This means that the user can't direct any
			// mouse input to the main app window, and the portfolio will eat up any
			// keyboard input.

			}
		}

#endif //WIN
#ifdef MAC
	RawRtn();
#endif //MAC

	/* Don't do this stuff unless we've got the CEX set up */
	if (vpcex != pvNil)
		{

		if (!fActive)
			{
			if (!vpcex->FCidIn(cidDeactivate))
				{
				vpcex->EnqueueCid(cidDeactivate);
				_fDown = fTrue;
				_cactToggle = 0;
				}
			}
		else if (_pcex != pvNil)
			{
			//
			// End the modal wait
			//
			Assert(CactModal() > 0, "AAAAAAAAAhhhhh! - P.Floyd (Encore performance)");

			// If there is no cidEndModal currently waiting to be processed,
			// enqueue one now. This ensures we don't get multiple cidEndModal's
			// processed. Note that we don't want to set _pcex null here as it
			// is later examined in the wndproc before we ultimately return
			// from FModalTopic.
			if (!_pcex->FCidIn(cidEndModal))
				_pcex->EnqueueCid(cidEndModal);

			}
		else
			{
			vpcex->FlushCid(cidDeactivate);	
			}

		}

}


/***************************************************************************
	Deactivate the app
***************************************************************************/
bool APP::FCmdDeactivate(PCMD pcmd)
{
	AssertThis(0);
	AssertVarMem(pcmd);

	GCB gcb;
	PWOKS pwoksModal;
	GTE gte;
	PGOB pgob;
	ulong grfgte;
	long lwRet;
	CMD_MOUSE cmd;
	PT pt;
	bool fDoQuit;

	if (_pcex != pvNil)
		{
		Assert(CactModal() > 0, "AAAAAAAAAhhhhh! - P.Floyd");
		return(fTrue);
		}

	pgob = vpcex->PgobTracking();
	if ((pgob != pvNil) && (_cactToggle < 500))
		{
		//
		// Toggle the mouse button
		//
		TrackMouse(pgob, &pt);

		ClearPb(&cmd, size(CMD_MOUSE));

		cmd.pcmh = pgob;
		cmd.cid = cidTrackMouse;
		cmd.xp = pt.xp;
		cmd.yp = pt.yp;
		cmd.grfcust = GrfcustCur();
		if (_fDown)
			{
			cmd.grfcust |= fcustMouse;
			}
		else
			{
			cmd.grfcust &= ~fcustMouse;
			}
		vpcex->EnqueueCmd((PCMD)&cmd);
		vpcex->EnqueueCid(cidDeactivate);
		_fDown = !_fDown;
		_cactToggle++;
		return fTrue;
		}

	gte.Init(Pkwa(), fgteNil);
	while (gte.FNextGob(&pgob, &grfgte, fgteNil))
		{
		if (!(grfgte & fgtePre) || !pgob->FIs(kclsGOK))
			continue;

		((PGOK)pgob)->Suspend();
		}

	if (FPushModal())
		{
		gcb.Set(CMH::HidUnique(), Pkwa(), fgobNil, kginMark);
		gcb._rcRel.Set(0, 0, krelOne, krelOne);

		_pcex = vpcex;

		if (pvNil != (pwoksModal = NewObj WOKS(&gcb, Pkwa()->Pstrg())))
			{
			vpcex->SetModalGob(pwoksModal);
			FModalLoop(&lwRet);  // If we cannot enter modal mode, then we just won't suspend.
			vpcex->SetModalGob(pvNil);
			}

		ReleasePpo(&pwoksModal);

		_pcex = pvNil;

		// The user may have selected Close the app system menu while the 
		// app was minimized on the taskbar. Depending on how Windows sent 
		// the messages to us, (ie the processing order of activate and 
		// close messages are not predictable it seems), we may or may not 
		// have a cidQuit message queued for the app. If PopModal destroys 
		// queued messages, then we would loose any Waiting cidQuit. 
		// Therefore check if we have a queued cidQuit, and if so, requeue 
		// it after the call to PopModal.
		fDoQuit = vpcex->FCidIn(cidQuit);

		PopModal();

		if(fDoQuit)
			vpcex->EnqueueCid(cidQuit);
		}


	gte.Init(Pkwa(), fgteNil);
	while (gte.FNextGob(&pgob, &grfgte, fgteNil))
		{
		if (!(grfgte & fgtePre) || !pgob->FIs(kclsGOK))
			continue;

		((PGOK)pgob)->Resume();
		}

	return(fTrue);
}


/***************************************************************************
	Copy pixels from an offscreen buffer (pgnvSrc, prcSrc) to the screen
	(pgnvDst, prcDst).  This is called to move bits from an offscreen
	buffer to the screen during a _FastUpdate cycle.  This gives us
	a chance to do a transition.
***************************************************************************/
void APP::_CopyPixels(PGNV pgnvSrc, RC *prcSrc, PGNV pgnvDst, RC *prcDst)
{
	AssertBaseThis(0);
	AssertPo(pgnvSrc, 0);
	AssertVarMem(prcSrc);
	AssertPo(pgnvDst, 0);
	AssertVarMem(prcDst);

	PMVIE pmvie = _Pmvie(); // Get the current movie, if any
	PGOB pgob;
	RC rcDst, rcSrc, rcWorkspace;

	if (pmvie == pvNil || pmvie->Trans() == transNil)
		{
		APP_PAR::_CopyPixels(pgnvSrc, prcSrc, pgnvDst, prcDst);
		return;
		}

	Assert(prcSrc->Dyp() == prcDst->Dyp() && prcSrc->Dxp() == prcDst->Dxp(),
		"rc's are scaled");

	// Need to do a transition, but if it's a slow transition (not a cut),
	// we want to do a regular copy on all the areas around the workspace,
	// then the slow transition on just the workspace.

	pgob = Pkwa()->PgobFromHid(kidWorkspace);

	if (pgob == pvNil || pmvie->Trans() == transCut)
		{
		pmvie->DoTrans(pgnvDst, pgnvSrc, prcDst, prcSrc);
		return;
		}

	// NOTE: This code assumes that the following will base rcWorkspace
	// in the same coordinate system as prcDst, which is always true
	// according to ShonK.
	pgob->GetRc(&rcWorkspace, cooHwnd);

	// Do the areas around the workspace without the transition
	if (prcDst->ypTop < rcWorkspace.ypTop)
		{
		rcSrc = *prcSrc;
		rcSrc.ypBottom = rcWorkspace.ypTop + (rcSrc.ypTop - prcDst->ypTop);
		rcDst = *prcDst;
		rcDst.ypBottom = rcWorkspace.ypTop;
		APP_PAR::_CopyPixels(pgnvSrc, &rcSrc, pgnvDst, &rcDst);
		}

	if (prcDst->ypBottom > rcWorkspace.ypBottom)
		{
		rcSrc = *prcSrc;
		rcSrc.ypTop = rcWorkspace.ypBottom + (rcSrc.ypTop - prcDst->ypTop);
		rcDst = *prcDst;
		rcDst.ypTop = rcWorkspace.ypBottom;
		APP_PAR::_CopyPixels(pgnvSrc, &rcSrc, pgnvDst, &rcDst);
		}
	if (prcDst->xpLeft < rcWorkspace.xpLeft)
		{
		rcSrc.ypTop = rcWorkspace.ypTop + (prcSrc->ypTop - prcDst->ypTop);
		rcSrc.ypBottom = rcWorkspace.ypBottom + (prcSrc->ypTop - prcDst->ypTop);
		rcSrc.xpLeft = prcSrc->xpLeft;
		rcSrc.xpRight = rcWorkspace.xpLeft + (prcSrc->xpLeft - prcDst->xpLeft);
		rcDst = *prcDst;
		rcDst.xpRight = rcWorkspace.xpLeft;
		rcDst.ypTop = rcWorkspace.ypTop;
		rcDst.ypBottom = rcWorkspace.ypBottom;
		APP_PAR::_CopyPixels(pgnvSrc, &rcSrc, pgnvDst, &rcDst);
		}

	if (prcDst->xpRight > rcWorkspace.xpRight)
		{
		rcSrc.ypTop = rcWorkspace.ypTop + (prcSrc->ypTop - prcDst->ypTop);
		rcSrc.ypBottom = rcWorkspace.ypBottom + (prcSrc->ypTop - prcDst->ypTop);
		rcSrc.xpLeft = rcWorkspace.xpRight + (prcSrc->xpLeft - prcDst->xpLeft);
		rcSrc.xpRight = prcSrc->xpRight;
		rcDst = *prcDst;
		rcDst.xpLeft = rcWorkspace.xpRight;
		rcDst.ypTop = rcWorkspace.ypTop;
		rcDst.ypBottom = rcWorkspace.ypBottom;
		APP_PAR::_CopyPixels(pgnvSrc, &rcSrc, pgnvDst, &rcDst);
		}

	//
	// Now do the workspace copy, with the transition
	//
	if (rcWorkspace.FIntersect(prcDst))
		{
		rcSrc = rcWorkspace;
		rcSrc.Offset(prcSrc->xpLeft - prcDst->xpLeft, prcSrc->ypTop - prcDst->ypTop);
		pmvie->DoTrans(pgnvDst, pgnvSrc, &rcWorkspace, &rcSrc);
		}
}



/***************************************************************************
	Load the Studio
***************************************************************************/
bool APP::FCmdLoadStudio(PCMD pcmd)
{
	AssertThis(0);
	AssertVarMem(pcmd);

	FNI fniUserDoc;
	CHID chidProject;
	long kidBuilding;
	PGOB pgob;
	PGOK pgokBackground;

	kidBuilding = pcmd->rglw[0];
	chidProject = pcmd->rglw[1];

	GetPortfolioDoc(&fniUserDoc); // might be ftgNil

	if (_FInitStudio(&fniUserDoc))
		{
		// Nuke the building gob
		pgob = Pkwa()->PgobFromHid(kidBuilding);
		if (pvNil != pgob)	
			pgob->Release();
	
		// Start a project, if requested
		if (chidProject != chidNil)
			{
			pgokBackground = (PGOK)Pkwa()->PgobFromHid(kidBackground);
			if ((pgokBackground != pvNil) && pgokBackground->FIs(kclsGOK))
				{
				AssertPo(pgokBackground, 0);
				// REVIEW *****: if this fails, what happens?
				pgokBackground->FRunScript((kstDefault << 16) | chidProject);
				}
			}
		}
	else
		{
		Assert(_pstdio == pvNil, "_FInitStudio didn't clean up");
		vpcex->EnqueueCid(cidLoadStudioFailed);
		}

	return fTrue;
}


/***************************************************************************
	Load the Building
***************************************************************************/
bool APP::FCmdLoadBuilding(PCMD pcmd)
{
	AssertThis(0);
	AssertVarMem(pcmd);

	if ((_pstdio != pvNil) && !_pstdio->FShutdown())
		{
		long kgobReturn;

		// This attempt may have destroyed the contents of kpridBuildingGob;
		// Try to reset it from kpridBuildingGobT.
		if (FGetProp(kpridBuildingGobT, &kgobReturn))
			FSetProp(kpridBuildingGob, kgobReturn);
		if (FGetProp(kpridBuildingStateT, &kgobReturn))
			FSetProp(kpridBuildingState, kgobReturn);
		Warn("Shutting down Studio failed");
		return fTrue;
		}

	if(_FInitBuilding())
		ReleasePpo(&_pstdio);
	return fTrue;
}


/***************************************************************************
	Exit the studio
***************************************************************************/
bool APP::FCmdExitStudio(PCMD pcmd)
{
	AssertThis(0);
	AssertVarMem(pcmd);

	bool tRet;
	STN stnBackup;

	// Now query the user to find whether they want to go to the
	// building or to exit the app completely.

	if (!FGetStnApp(idsExitStudio, &stnBackup))
		return fTrue;

	tRet = TModal(vpapp->PcrmAll(), ktpcQueryExitStudio, &stnBackup, bkYesNoCancel);

	// Take more action if the user did not hit cancel.
	if(tRet != tMaybe)
		{
		// Save the current movie if necessary.
		if ((_pstdio != pvNil) && !_pstdio->FShutdown(tRet == tYes))
			return fTrue;

		// Either go to the building, or leave the app.
		if (tRet == tYes)
			{
			// Go to the building.
			if(_FInitBuilding())
				ReleasePpo(&_pstdio);
			}
		else if (tRet == tNo)
			{
			// User wants to quit. We have have any dirty doc already by now.
			// Note that APP::Quit() doesn't release _pstdio when we quit so
			// we don't need to do it here either.
			_fQuit = fTrue;
			}
		}

	return fTrue;
}


/***************************************************************************
	Load the Theater
***************************************************************************/
bool APP::FCmdTheaterOpen(PCMD pcmd)
{
	AssertThis(0);
	AssertVarMem(pcmd);

	long kidParent;

	kidParent = pcmd->rglw[0];

	if (pvNil != _ptatr)
		{
		Bug("You forgot to close the last TATR!");
		AssertPo(_ptatr, 0);
		return fTrue;
		}
	_ptatr = TATR::PtatrNew(kidParent);
	// Let the script know whether the open succeeded or failed
	vpcex->EnqueueCid(cidTheaterOpenCompleted, pvNil, pvNil, (pvNil != _ptatr));

	return fTrue;
}

/***************************************************************************
	Close the Theater
***************************************************************************/
bool APP::FCmdTheaterClose(PCMD pcmd)
{
	AssertThis(0);
	AssertVarMem(pcmd);

	AssertPo(_ptatr, 0);
	ReleasePpo(&_ptatr);

	return fTrue;
}


/***************************************************************************
	Clear the portfolio doc
***************************************************************************/
bool APP::FCmdPortfolioClear(PCMD pcmd)
{
	AssertThis(0);
	AssertVarMem(pcmd);

	_fniPortfolioDoc.SetNil();

	return fTrue;
}


/***************************************************************************
	Display the customized open file common dlg.
***************************************************************************/
bool APP::FCmdPortfolioOpen(PCMD pcmd)
{
	AssertThis(0);
	AssertVarMem(pcmd);

	FNI fni;
	bool fOKed;
	long idsTitle, idsFilterLabel, idsFilterExt;
	FNI fniUsersDir;
	PFNI pfni;
	ulong grfPrevType;
	CNO cnoWave = cnoNil;

	// Set up strings specific to this use of the portfolio.

	switch (pcmd->rglw[0])
		{
	case kpfPortOpenMovie:
		idsTitle = idsPortfOpenMovieTitle;
		idsFilterLabel = idsPortfMovieFilterLabel;
		idsFilterExt = idsPortfMovieFilterExt;
		
		grfPrevType = fpfPortPrevMovie;
		cnoWave = kwavPortOpenMovie;

		break;

	case kpfPortOpenSound:
		// Only display extensions appropriate to the type of sound being imported.
		idsTitle = idsPortfOpenSoundTitle;
 		idsFilterLabel = idsPortfSoundFilterLabel;
		if(pcmd->rglw[1] == kidMidiGlass)
			idsFilterExt = idsPortfSoundMidiFilterExt;
		else
			idsFilterExt = idsPortfSoundWaveFilterExt;

		grfPrevType = fpfPortPrevMovie | fpfPortPrevSound;
		cnoWave = kwavPortOpenSound;

		break;

	case kpfPortOpenTexture:
		idsTitle = idsPortfOpenTextureTitle;
 		idsFilterLabel = idsPortfTextureFilterLabel;
		idsFilterExt = idsPortfTextureFilterExt;

		grfPrevType = fpfPortPrevTexture;
		// Currently no audio for open texture, because we don't open texture yet.

		break;

	default:
		Bug("Unrecognized portfolio open type.");
		return fFalse;
		}

	// Prepare to set the initial directory for the portfolio if necessary.

	switch (pcmd->rglw[2])
		{
	case kpfPortDirUsers:

		// Initial directory will be the 'Users' directory.

		vapp.GetFniUsers(&fniUsersDir);
		pfni = &fniUsersDir;

		break;

	default:

		// Initial directory will be current directory.

		pfni = pvNil;

		break;
		}

	// Now display the open dlg. Script is informed of the outcome
	// of the portfolio from beneath FPortDisplayWithIds.
	fOKed = FPortDisplayWithIds(&fni, fTrue, idsFilterLabel, idsFilterExt,
		idsTitle, pvNil, pvNil, pfni, grfPrevType, cnoWave);
	if (fOKed)
		{
		// User selected a file, so store fni for later use.
		SetPortfolioDoc(&fni);
		}

	return fTrue;
}


/******************************************************************************
	OnnDefVariable
		Retrieves the default onn for this app.  Gets the name from the app's
		string table.
************************************************************ PETED ***********/
long APP::OnnDefVariable(void)
{
	AssertBaseThis(0);

	if (_onnDefVariable == onnNil)
		{
		STN stn;

		if (!FGetStnApp(idsDefaultFont, &stn) ||
				!FGetOnn(&stn, &_onnDefVariable))
			{
			_onnDefVariable = APP_PAR::OnnDefVariable();
			}
		}
	return _onnDefVariable;
}


/******************************************************************************
	FGetOnn
		APP version of FGetOnn.  Mainly used so that we can easily report
		failure to the user if the font isn't around.  And no, I don't care
		that we'll call FGetOnn twice on failure...it's a failure case, and
		can stand to be slow.  :)  Maps the font on failure so that there's
		still a usable onn if the user doesn't want to mess with alternate
		solutions.
	
	Arguments:
		PSTN pstn   --  The font name
		long *ponn  --  takes the result
	
	Returns:  fTrue if the original font could be found.
	
************************************************************ PETED ***********/
bool APP::FGetOnn(PSTN pstn, long *ponn)
{
	AssertBaseThis(0);

	if (!vntl.FGetOnn(pstn, ponn))
		{
		if (!_fFontError)
			{
			PushErc(ercSocNoDefaultFont);
			_fFontError = fTrue;
			}
		*ponn = vntl.OnnMapStn(pstn);
		return fFalse;
		}
	return fTrue;
}


/***************************************************************************
	Return the size of memory
***************************************************************************/
void APP::MemStat(long *pdwTotalPhys, long *pdwAvailPhys)
{
#ifdef WIN
	MEMORYSTATUS ms;
	ms.dwLength = size(MEMORYSTATUS);
	GlobalMemoryStatus(&ms);
	if (pvNil != pdwTotalPhys)
		*pdwTotalPhys = ms.dwTotalPhys;
	if (pvNil != pdwAvailPhys)
		*pdwAvailPhys = ms.dwAvailPhys;
#else
	RawRtn();
#endif //!WIN
}


/******************************************************************************
	DypTextDef
		Retrieves the default dypFont for this app.  Gets the font size as
		a string from the app's string table, then converts it to the number
		value.
************************************************************ PETED ***********/
long APP::DypTextDef(void)
{
	AssertBaseThis(0);

	if (_dypTextDef == 0)
		{
		STN stn;

		if (pvNil == _pgstApp || !FGetStnApp(idsDefaultDypFont, &stn) ||
			!stn.FGetLw(&_dypTextDef) || _dypTextDef <= 0)
			{
			Warn("DypTextDef failed");
			_dypTextDef = APP_PAR::DypTextDef();
			}
		}
	return _dypTextDef;
}


/***************************************************************************
	Ask the user if they want to save changes to the given doc.
***************************************************************************/
bool APP::TQuerySaveDoc(PDOCB pdocb, bool fForce)
{
	AssertThis(0);
	AssertPo(pdocb, 0);

	STN stnName;
	long tpc;
	STN stnBackup;
	long bk;
	bool tResult;

	pdocb->GetName(&stnName);
	tpc = fForce ? ktpcQuerySave : ktpcQuerySaveWithCancel;
	if (!FGetStnApp(idsSaveChangesBkp, &stnBackup))
		stnBackup.SetNil();
	bk = fForce? bkYesNo : bkYesNoCancel;

	tResult = TModal(vpapp->PcrmAll(), tpc, &stnBackup, bk, kstidQuerySave,
		&stnName);
	vpcex->EnqueueCid(cidQuerySaveDocResult, pvNil, pvNil, tResult);

	return tResult;
}


/***************************************************************************
	Quit routine.  May or may not initiate the quit sequence (depending
	on user input).
***************************************************************************/
void APP::Quit(bool fForce)
{
	AssertThis(0);

	bool tRet;
	STN stnBackup;

	// If we already know we have to quit, or a modal topic is already
	// being  displayed, then do not query the user to quit here.
	if (_fQuit || vpappb->CactModal() > (_pcex != pvNil? 1 : 0)
			|| FInPortfolio())
		{
		// Make sure the app is visible to the user. Otherwise if this
		// return is preventing a system shutdown and we're minimized
		// on the taskbar, then the user won't know why the shutdown failed.
		if (!_fQuit)
			EnsureInteractive();
		return;
		}

	if (fForce)
		{
		// Force quit, don't ask the user if they want to quit.  But
		// do ask if they want to save their documents.
		DOCB::FQueryCloseAll(fdocForceClose);
		_fQuit = fTrue;

		return;
		}

	// If we're minimized, user is closing app from the taskbar.  Quit
	// without confirmation (we'll still confirm movie save if user has
	// a dirty doc)
	if (_fMinimized)
		{
		tRet = tYes;
		}
	else
		{
		if (!FGetStnApp(idsConfirmExitBkp, &stnBackup))
			stnBackup.SetNil();
		tRet = TModal(vpapp->PcrmAll(), ktpcQueryQuit, &stnBackup, bkYesNo);
		}

	if (tRet == tYes)
		{
		// User wants to quit, so shut down studio if necessary
		if (_pstdio == pvNil || _pstdio->FShutdown(fFalse))
			_fQuit = fTrue;
		}

}


/***************************************************************************
	Return a pointer to the current movie, if any.  The movie could be in
	the studio, theater, or splot machine.
***************************************************************************/
PMVIE APP::_Pmvie(void)
{
	AssertBaseThis(0);

	PMVIE pmvie = pvNil;
	PSPLOT psplot;

	if (_pstdio != pvNil && _pstdio->Pmvie() != pvNil)
		pmvie = _pstdio->Pmvie();
	else if (_ptatr != pvNil && _ptatr->Pmvie() != pvNil)
		pmvie = _ptatr->Pmvie();
	else if (Pkwa() != pvNil)
		{
		psplot = (PSPLOT)Pkwa()->PgobFromCls(kclsSPLOT);
		if (psplot != pvNil)
			pmvie = psplot->Pmvie();
		}
	return pmvie;
}


/****************************************
	Info dialog items (idits)
****************************************/
enum
	{
	iditOkInfo,
	iditProductId,
	iditWindowModeInfo,
#ifdef DEBUG
	iditCactAV,
#endif // DEBUG
	iditProductNameInfo,
	iditSaveChanges,
	iditRenderModeInfo,
	iditLimInfo
	};


#ifdef WIN

/***************************************************************************
	Useful function
***************************************************************************/
char *LoadGenResource(HINSTANCE hInst, LPCSTR lpResource, LPCSTR lpType)
{
	HRSRC hResource;
	HGLOBAL hGbl;

	hResource = FindResource(hInst, lpResource, lpType);

	if (hResource == NULL)
		return(NULL);

	hGbl = LoadResource(hInst, hResource);

	return (char *)LockResource(hGbl);
}

#endif

/***************************************************************************
	Put up info dialog
***************************************************************************/
bool APP::FCmdInfo(PCMD pcmd)
{
	AssertThis(0);
	PMVIE pmvie = pvNil;
	PDLG pdlg;
	long idit;
	bool fRunInWindowNew;
	STN stn;
	STN stnT;
	bool fSaveChanges;

	pmvie = _Pmvie();

	pdlg = DLG::PdlgNew(dlidInfo, pvNil, pvNil);
	if (pvNil == pdlg)
		return fTrue;
	pdlg->PutRadio(iditRenderModeInfo, _fSlowCPU ? 1 : 0);

	GetStnAppName(&stn);
#ifdef DEBUG
	stn.FAppendSz(PszLit(" (Debug)"));
#endif //DEBUG
	stnT.FFormatSz(PszLit(" %d.%d.%04d"), rmj, rmm, rup);
	stn.FAppendStn(&stnT);
	pdlg->FPutStn(iditProductNameInfo, &stn);

#ifdef WIN
	LPSTR lpszPid;
	char rgchPid[kcchMaxStn + 1];

	lpszPid = (LPSTR)LoadGenResource(vwig.hinst, MAKEINTRESOURCE(RC_PID_NUMBER), RT_RCDATA);

	if(lpszPid != pvNil)
		{
	
		#define PID_RPC       128
		#define PID_SITE      (PID_RPC + 5)
		#define PID_SERIAL    (PID_SITE + 3)
		#define PID_RANDOM    (PID_SERIAL + 7)

		wsprintf(rgchPid,
				  "%5.5s-%3.3s-%7.7s-%5.5s",
				  lpszPid+PID_RPC,
				  lpszPid+PID_SITE,
				  lpszPid+PID_SERIAL,
				  lpszPid+PID_RANDOM);
	
		stn.SetSz(rgchPid);
		pdlg->FPutStn(iditProductId, &stn);
		}
#endif

#ifdef DEBUG
	pdlg->FPutLwInEdit(iditCactAV, vcactAV);
#endif // DEBUG
	pdlg->PutRadio(iditWindowModeInfo, _fRunInWindow ? 1 : 0);

	idit = pdlg->IditDo();

	fSaveChanges = pdlg->FGetCheck(iditSaveChanges);
	if (FPure(_fSlowCPU) != FPure(pdlg->LwGetRadio(iditRenderModeInfo)))
		{
		PMVIE pmvie;

		_fSlowCPU = !_fSlowCPU;
		pmvie = _Pmvie();
		if (pvNil != pmvie)
			{
			pmvie->Pbwld()->FSetHalfMode(fFalse, _fSlowCPU);
			pmvie->InvalViews();
			}
		}
	if (fSaveChanges)
		{
		bool fSlowCPU = _fSlowCPU;
		FGetSetRegKey(kszBetterSpeedValue, &fSlowCPU,
			size(bool), fregSetKey);
		}

	fRunInWindowNew = pdlg->LwGetRadio(iditWindowModeInfo);
	if (FPure(_fRunInWindow) != FPure(fRunInWindowNew))
		{
		if (!fRunInWindowNew)
			{
			// user wants to be fullscreen
			if (_FDisplaySwitchSupported())
				{
				_fRunInWindow = fFalse;
				_RebuildMainWindow();
				if (!_FSwitch640480(fTrue))
					{
					_fRunInWindow = fTrue;
					_RebuildMainWindow();
					}
				}
			}
		else
			{
			// user wants to run in a window.
			// Don't allow user to run in a window at 640x480 resolution.
			if (!_FDisplayIs640480() || _fSwitchedResolution)
				{
				_fRunInWindow = fTrue;
				_RebuildMainWindow();
				if (_FSwitch640480(fFalse))
					{
					_fSwitchedResolution = fFalse;
					}
				else
					{
					// back to fullscreen
					_fRunInWindow = fFalse;
					_RebuildMainWindow();
					}
				}
			}
		}
	if (fSaveChanges)
		{
		bool fSwitchRes = !_fRunInWindow;

		FGetSetRegKey(kszSwitchResolutionValue, &fSwitchRes,
			size(fSwitchRes), fregSetKey);
		}

#ifdef DEBUG
	{
	bool fEmpty;
	long lwT;

	if (pdlg->FGetLwFromEdit(iditCactAV, &lwT, &fEmpty) && !fEmpty)
		{
		if (lwT < 0)
			{
			Debugger();
			}
		else
			{
			vcactAV = lwT;
			if (fSaveChanges)
				{
				DBINFO dbinfo;

				dbinfo.cactAV = vcactAV;
				AssertDo(FGetSetRegKey(PszLit("DebugSettings"), &dbinfo, size(DBINFO),
					fregSetKey | fregBinary),
					"Couldn't save current debug settings in registry");
				}
			}
		}
	}
#endif // DEBUG

	ReleasePpo(&pdlg);

	return fTrue;
}


#ifdef WIN
#ifdef UNICODE
typedef LONG (WINAPI *PFNCHDS) (LPDEVMODEW lpDevMode, DWORD dwFlags);
const PSZ kpszChds = PszLit("ChangeDisplaySettingsW");
#else
typedef LONG (WINAPI *PFNCHDS) (LPDEVMODEA lpDevMode, DWORD dwFlags);
const PSZ kpszChds = PszLit("ChangeDisplaySettingsA");
#endif // !UNICODE

#ifdef BUG1920
#ifdef UNICODE
typedef BOOL (WINAPI *PFNENUM) (LPCWSTR lpszDeviceName, DWORD iModeNum, LPDEVMODEW lpDevMode);
const PSZ kpszEnum = PszLit("EnumDisplaySettingsW");
#else
typedef BOOL (WINAPI *PFNENUM) (LPCSTR lpszDeviceName, DWORD iModeNum, LPDEVMODEA lpDevMode);
const PSZ kpszEnum = PszLit("EnumDisplaySettingsA");
#endif // !UNICODE
#endif // BUG1920
#endif //WIN

#ifndef DM_BITSPERPEL
#define DM_BITSPERPEL 0x00040000L // from wingdi.h
#define DM_PELSWIDTH 0x00080000L
#define DM_PELSHEIGHT 0x00100000L
#endif //!DM_BITSPERPEL

#ifndef CDS_FULLSCREEN
#define CDS_FULLSCREEN 4
#endif //!CDS_FULLSCREEN

#ifndef DISP_CHANGE_SUCCESSFUL
#define DISP_CHANGE_SUCCESSFUL 0
#endif //!DISP_CHANGE_SUCCESSFUL

/***************************************************************************
	Determine if display resolution switching is supported
***************************************************************************/
bool APP::_FDisplaySwitchSupported(void)
{
	AssertBaseThis(0);

#ifdef WIN
	DWORD dwVersion;
	byte bVersionMajor;
	byte bVersionMinor;
	bool fNT;
	
	dwVersion = GetVersion();
	bVersionMinor = (byte)((dwVersion & 0x0000ff00) >> 8);
	bVersionMajor = (byte)(dwVersion & 0x000000ff);
	fNT = ((dwVersion & 0x80000000) == 0);
	if (fNT)
		{
		// Note: At this time, no version of NT supports res-switching.  If
		//       this changes in the future, do the version-check here.  We
		//       can't just try ChangeDisplaySettings to see if it works,
		//       because on NT 3.51, the API exists, but hangs when we call
		//       it.
		return fFalse;
		}
	else
		{
		if (bVersionMajor >= 4)
			return fTrue;
		}
#endif //WIN
#ifdef MAC
	RawRtn();
#endif //MAC
	return fFalse; // OS doesn't support res-switching
}


/***************************************************************************
	Switch to/from 640x480x8bit video mode.  It uses GetProcAddress so it
	can fail gracefully on systems that don't support
	ChangeDisplaySettings().
***************************************************************************/
bool APP::_FSwitch640480(bool fTo640480)
{
	AssertBaseThis(0);

#ifdef WIN
#ifdef BUG1920
	bool fSetMode = fFalse, fSetBbp = fTrue;
	DWORD iModeNum;
	PFNENUM pfnEnum;
#endif // BUG1920
	HINSTANCE hLibrary;
	PFNCHDS pfnChds;
	DEVMODE devmode;
	long lwResult;

	hLibrary = LoadLibrary(PszLit("USER32.DLL"));
	if (0 == hLibrary)
		goto LFail;

	pfnChds = (PFNCHDS)GetProcAddress(hLibrary, kpszChds);
	if (pvNil == pfnChds)
		goto LFail;

#ifdef BUG1920
	pfnEnum = (PFNENUM)GetProcAddress(hLibrary, kpszEnum);
	if (pvNil == pfnEnum)
		goto LFail;
#endif // BUG1920

	if (fTo640480)
		{
		// Try to switch to 640x480
#ifdef BUG1920
LRetry:
		for (iModeNum = 0; pfnEnum(NULL, iModeNum, &devmode); iModeNum++)
			{
			if ((fSetBbp ? devmode.dmBitsPerPel != 8 :
					devmode.dmBitsPerPel < 8) || devmode.dmPelsWidth != 640 ||
					devmode.dmPelsHeight != 480)
				{
				continue;
				}
			lwResult = pfnChds(&devmode, CDS_FULLSCREEN);
			if (lwResult == DISP_CHANGE_SUCCESSFUL)
				{
				fSetMode = fTrue;
				break;
				}
			}
		if (!fSetMode && fSetBbp)
			{
			fSetBbp = fFalse;
			goto LRetry;
			}

		if (fSetMode && _FDisplayIs640480())
#else  // BUG1920
		devmode.dmSize = size(DEVMODE);
		devmode.dmBitsPerPel = 8;
		devmode.dmPelsWidth = 640;
		devmode.dmPelsHeight = 480;
		devmode.dmFields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT;
		lwResult = pfnChds(&devmode, CDS_FULLSCREEN);
		if (DISP_CHANGE_SUCCESSFUL != lwResult)
			{
			// try without setting the bpp
			devmode.dmFields &= ~DM_BITSPERPEL;
			lwResult = pfnChds(&devmode, CDS_FULLSCREEN);
			}

		if (DISP_CHANGE_SUCCESSFUL == lwResult && _FDisplayIs640480())
#endif  // !BUG1920
			{
			_fSwitchedResolution = fTrue;
			SetWindowPos(vwig.hwndApp, HWND_TOP, 0, 0, 640, 480, 0);
			}
		else
			{
			goto LFail;
			}
		}
	else
		{
		// Try to restore user's previous resolution
		lwResult = pfnChds(NULL, CDS_FULLSCREEN);
		if (DISP_CHANGE_SUCCESSFUL != lwResult)
			goto LFail;
		}
	FreeLibrary(hLibrary);
	return fTrue;
LFail:
	if (0 != hLibrary)
		FreeLibrary(hLibrary);
#endif //WIN
#ifdef MAC
	RawRtn();
#endif //MAC
	return fFalse;
}


/***************************************************************************
	Clean up routine - app is shutting down
***************************************************************************/
void APP::_CleanUp(void)
{
	_FWriteUserData();
	ReleasePpo(&_pstdio);
	ReleasePpo(&_ptatr);
	ReleasePpo(&_pcfl);
	ReleasePpo(&_pmvieHandoff);
	ReleasePpo(&_pcrmAll);
	ReleasePpo(&_pglicrfBuilding);
	ReleasePpo(&_pglicrfStudio);
	ReleasePpo(&_pgstBuildingFiles);
	ReleasePpo(&_pgstStudioFiles);
	ReleasePpo(&_pgstSharedFiles);
	ReleasePpo(&_pgstApp);
	ReleasePpo(&_pkwa);
	BWLD::CloseBRender();
	APP_PAR::_CleanUp();
	if (_fSwitchedResolution)
		_FSwitch640480(fFalse); // try to restore desktop
}


/***************************************************************************
	Put up a modal help balloon
***************************************************************************/
bool APP::TModal(PRCA prca, long tpc, PSTN pstnBackup, long bkBackup,
	long stidSubst, PSTN pstnSubst)
{
	AssertThis(0);
	AssertNilOrPo(prca, 0);

	long lwSelect;
	bool tRet;
	STN stn;

	// If app is minimized, restore it so user can see the dialog
	EnsureInteractive();

	if (ivNil != stidSubst)
		{
		AssertPo(pstnSubst, 0);
		if (!Pkwa()->Pstrg()->FPut(stidSubst, pstnSubst))
			return tMaybe;
		}

	if (pvNil == prca || !Pkwa()->FModalTopic(prca, tpc, &lwSelect))
		{
		// Backup plan: use old TGiveAlertSz.
		if (pvNil != pstnSubst)
			stn.FFormat(pstnBackup, pstnSubst);
		else
			stn = *pstnBackup;
		tRet = TGiveAlertSz(stn.Psz(), bkBackup, cokExclamation);
		}
	else
		{
		// Help balloon appeared ok, so translate returned value into
		// something we can use here.  lwSelect is returned as the
		// (1 based) index of the btn clicked, out of Yes/No/Cancel.
		switch (lwSelect)
			{
		case 1:
			tRet = tYes;
			break;
		case 2:
			tRet = tNo;
			break;
		case 3:
			tRet = tMaybe;
			break;
		default:
			Bug("TModal() balloon returned unrecognized selection");
			tRet = tMaybe;
			}
		}

	if (ivNil != stidSubst)
		Pkwa()->Pstrg()->Delete(stidSubst);
	UpdateMarked(); // make sure alert is drawn over
	return tRet;
}


/***************************************************************************
	Static function to prompt the user to insert the CD named pstnTitle
***************************************************************************/
bool APP::FInsertCD(PSTN pstnTitle)
{
	AssertPo(pstnTitle, 0);

	STN stnBackup;
	bool tRet;

	stnBackup = PszLit("I can't find the CD '%s'  Please insert it.  Should I look again?");
	tRet = vpapp->TModal(vpapp->PcrmAll(), ktpcQueryCD, &stnBackup, bkYesNo,
		kstidQueryCD, pstnTitle);

	/* Don't tell the user that they told us not to try again; they know
		that already */
	if (tRet == tNo)
		vapp._fDontReportInitFailure = fTrue;

	return (tYes == tRet);
}


/******************************************************************************
	DisplayErrors
		Displays any errors that happen to be on the error stack.  Call this
		when you don't want to wait until idle time to show errors.
************************************************************ PETED ***********/
void APP::DisplayErrors(void)
{
	AssertThis(0);

	long erc;
	STN stnMessage;

	if (vpers->FPop(&erc))
		{
		STN stnErr;

		vpers->Clear();

		//
		// Convert to help topic number
		//
		switch (erc)
			{
		case ercOomHq:
			erc = ktpcercOomHq;
			break;

		case ercOomPv:
			erc = ktpcercOomPv;
			break;

		case ercOomNew:
			erc = ktpcercOomNew;
			break;

		/* We don't have any real specific information to present the user in
			these cases, plus they generally shouldn't come up anyway (a more
			informational error should have been pushed farther up the chain
			of error reporters) */
		case ercFilePerm:
		case ercFileOpen:
		case ercFileCreate:
		case ercFileSwapNames:
		case ercFileRename:
		case ercFniGeneral:
		case ercFniDelete:
		case ercFniRename:
		case ercFniMismatch:
		case ercFniDirCreate:
		case ercFneGeneral:
		case ercCflCreate:
		case ercCflSaveCopy:
		case ercSndmCantInit:
		case ercSndmPartialInit:
		case ercGfxCantDraw:
		case ercGfxCantSetFont:
		case ercGfxNoFontList:
		case ercGfxCantSetPalette:
		case ercDlgCantGetArgs:
		case ercDlgCantFind:
		case ercRtxdTooMuchText:
		case ercRtxdReadFailed:
		case ercRtxdSaveFailed:
		case ercCantOpenVideo:
		case ercMbmpCantOpenBitmap:

		/* In theory, these are obsolete. */
		case ercSocTdtTooLong:
		case ercSocWaveInProblems:
		case ercSocCreatedUserDir:

			/* Display a generic error message, with the error code in it */
			stnErr.FFormatSz(PszLit("%d"), erc);
			if (!vapp.Pkwa()->Pstrg()->FPut(kstidGenericError, &stnErr))
				stnErr.SetNil();
			erc = ktpcercSocGenericError;
			break;

		case ercFileGeneral:
			erc = ktpcercFileGeneral;
			break;

		case ercCflOpen:
			erc = ktpcercCflOpen;
			break;

		case ercCflSave:
			erc = ktpcercCflSave;
			break;

		case ercCrfCantLoad:
			erc = ktpcercCrfCantLoad;
			break;

		case ercFniHidden:
			erc = ktpcercFniHidden;
			break;

		case ercOomGdi:
			erc = ktpcercOomGdi;
			break;

		case ercDlgOom:
			erc = ktpcercDlgOom;
			break;

		case ercCantSave:
			erc = ktpcercCantSave;
			break;

		case ercSocSaveFailure:
			erc = ktpcercSocSaveFailure;
			break;

		case ercSocSceneSwitch:
			erc = ktpcercSocSceneSwitch;
			break;

		case ercSocSceneChop:
			erc = ktpcercSocSceneChop;
			break;

		case ercSocBadFile:
			erc = ktpcercSocBadFile;
			break;

		case ercSocNoTboxSelected:
			erc = ktpcercSocNoTboxSelected;
			break;

		case ercSocNoActrSelected:
			erc = ktpcercSocNoActrSelected;
			break;

		case ercSocNotUndoable:
			erc = ktpcercSocNotUndoable;
			break;

		case ercSocNoScene:
			erc = ktpcercSocNoScene;
			break;

		case ercSocBadVersion:
			erc = ktpcercSocBadVersion;
			break;

		case ercSocNothingToPaste:
			erc = ktpcercSocNothingToPaste;
			break;

		case ercSocBadFrameSlider:
			erc = ktpcercSocBadFrameSlider;
			break;

		case ercSocGotoFrameFailure:
			erc = ktpcercSocGotoFrameFailure;
			break;

		case ercSocDeleteBackFailure:
			erc = ktpcercSocDeleteBackFailure;
			break;

		case ercSocActionNotApplicable:
			erc = ktpcercSocActionNotApplicable;
			break;

		case ercSocCannotPasteThatHere:
			erc = ktpcercSocCannotPasteThatHere;
			break;

		case ercSocNoModlForChar:
			erc = ktpcercSocNoModlForChar;
			break;

		case ercSocNameTooLong:
			erc = ktpcercSocNameTooLong;
			break;

		case ercSocTboxTooSmall:
			erc = ktpcercSocTboxTooSmall;
			break;

		case ercSocNoThumbnails:
			erc = ktpcercSocNoThumbnails;
			break;

		case ercSocBadTdf:
			erc = ktpcercSocBadTdf;
			break;

		case ercSocNoActrMidi:
			erc = ktpcercSocNoActrMidi;
			break;

		case ercSocNoImportRollCall:
			erc = ktpcercSocNoImportRollCall;
			break;

		case ercSocNoNukeRollCall:
			erc = ktpcercSocNoNukeRollCall;
			break;

		case ercSocSceneSortError:
			erc = ktpcercSocSceneSortError;
			break;

		case ercSocCantInitSceneSort:
			erc = ktpcercSocCantInitSceneSort;
			break;

		case ercSocCantInitSplot:
			erc = ktpcercSocCantInitSplot;
			break;

		case ercSocNoWaveIn:
			erc = ktpcercSocNoWaveIn;
			break;

		case ercSocPortfolioFailed:
			erc = ktpcercSocPortfolioFailed;
			break;

		case ercSocCantInitStudio:
			erc = ktpcercSocCantInitStudio;
			break;

		case ercSoc3DWordCreate:
			erc = ktpcercSoc3DWordCreate;
			break;

		case ercSoc3DWordChange:
			erc = ktpcercSoc3DWordChange;
			break;

		case ercSocWaveSaveFailure:
			erc = ktpcercSocWaveSaveFailure;
			break;

		case ercSocNoSoundName:
			erc = ktpcercSocNoSoundName;
			break;

		case ercSndamWaveDeviceBusy:
			erc = ktpcercSndamWaveDeviceBusy;
			break;

		case ercSocNoKidSndsInMovie:
			erc = ktpcercSocNoKidSndsInMovie;
			break;
	
		case ercSocMissingMelanieDoc:
			erc = ktpcercSocMissingMelanieDoc;
			break;
	
		case ercSocCantLoadMelanieDoc:
			erc = ktpcercSocCantLoadMelanieDoc;
			break;

		case ercSocBadSoundFile:
			erc = ktpcercSocBadSoundFile;
			break;
	
		case ercSocBadSceneSound:
			erc = ktpcercSocBadSceneSound;
			break;
		
		case ercSocNoDefaultFont:
			erc = ktpcercSocNoDefaultFont;
			break;

		case ercSocCantCacheTag:
			erc = ktpcercSocCantCacheTag;
			break;
		
		case ercSocInvalidFilename:
			erc = ktpcercSocInvalidFilename;
			break;

		case ercSndMidiDeviceBusy:
			erc = ktpcercSndMidiDeviceBusy;
			break;

		case ercSocCantCopyMsnd:
			erc = ktpcercSocCantCopyMsnd;
			break;

		default:
			return;
			}

		FGetStnApp(idsOOM, &stnMessage);
		TModal(PcrmAll(), erc, &stnMessage, bkOk);
		if (stnErr.Cch() != 0)
			vapp.Pkwa()->Pstrg()->Delete(kstidGenericError);
		}
}


/***************************************************************************
	Idle routine.  Do APPB idle stuff, then report any runtime errors.
***************************************************************************/
bool APP::FCmdIdle(PCMD pcmd)
{
	AssertThis(0);
	AssertVarMem(pcmd);

	bool fFileError = fFalse;
	PCFL pcfl;

	APP_PAR::FCmdIdle(pcmd);

	/* Check all open chunky files for errors */
	for (pcfl = CFL::PcflFirst(); pcfl != pvNil; pcfl = (PCFL)pcfl->PbllNext())
		{
		if (pcfl->ElError() != elNil)
			{
			fFileError = fTrue;
			pcfl->ResetEl();
			}
		}
	/* Ensure that we report *something* if there was a file error */
	if (fFileError && vpers->Cerc() == 0)
		PushErc(ercFileGeneral);

	DisplayErrors();

	return fTrue;
}


#ifdef WIN
/***************************************************************************
	Tell another instance of the app to open a document.
***************************************************************************/
bool APP::_FSendOpenDocCmd(HWND hwnd, PFNI pfniUserDoc)
{
	AssertBaseThis(0);
	Assert(pvNil != hwnd, "bad hwnd");
	AssertPo(pfniUserDoc, ffniFile);

	STN stnUserDoc;
	STN stn;
	FNI fniTemp;
	PFIL pfil = pvNil;
	BLCK blck;
	DWORD dwProcId;

	// Write filename to 3DMMOpen.tmp in the temp dir
	pfniUserDoc->GetStnPath(&stnUserDoc);
	if (!fniTemp.FGetTemp())
		goto LFail;
	stn = kpszOpenFile;
	if (!fniTemp.FSetLeaf(&stn, kftgTemp))
		goto LFail;
	if (tYes == fniTemp.TExists())
		{
		// replace any existing open-doc request
		if (!fniTemp.FDelete())
			goto LFail;
		}
	pfil = FIL::PfilCreate(&fniTemp);
	if (pvNil == pfil)
		goto LFail;
	if (!pfil->FSetFpMac(stnUserDoc.CbData()))
		goto LFail;
	blck.Set(pfil, 0, stnUserDoc.CbData());
	if (!stnUserDoc.FWrite(&blck, 0))
		goto LFail;
	blck.Free(); // so it doesn't reference pfil anymore
	ReleasePpo(&pfil);
#ifdef WIN
	dwProcId = GetWindowThreadProcessId(hwnd, NULL);
	PostThreadMessage(dwProcId, WM_USER, klwOpenDoc, 0);
#else
	RawRtn();
#endif //!WIN
	return fTrue;
LFail:
	blck.Free(); // so it doesn't reference pfil anymore
	ReleasePpo(&pfil);
	return fFalse;
}
#endif //WIN


#ifdef WIN
/***************************************************************************
	Process a request (from another instance of the app) to open a document
***************************************************************************/
bool APP::_FProcessOpenDocCmd(void)
{
	AssertBaseThis(0);

	STN stnUserDoc;
	STN stn;
	FNI fniTemp;
	PFIL pfil = pvNil;
	FNI fniUserDoc;
	BLCK blck;

	// Find the temp file
	if (!fniTemp.FGetTemp())
		return fFalse;
	stn = kpszOpenFile;
	if (!fniTemp.FSetLeaf(&stn, kftgTemp))
		return fFalse;
	if (tYes != fniTemp.TExists())
		{
		Bug("Got a ProcessOpenDocCmd but there's no temp file!");
		return fFalse;
		}

	// See if we can accept open document commands now: If accelerators
	// are enabled, then ctrl-o is enabled, so open-document commands are
	// acceptable.
	if (_cactDisable > 0 || CactModal() > 0)
		goto LFail;
	
	// Read the document filename from temp file
	pfil = FIL::PfilOpen(&fniTemp);
	if (pvNil == pfil)
		goto LFail;
	blck.Set(pfil, 0, pfil->FpMac());
	if (!stnUserDoc.FRead(&blck, 0))
		goto LFail;
	blck.Free(); // so it doesn't reference pfil anymore
	ReleasePpo(&pfil);

	if (!fniUserDoc.FBuildFromPath(&stnUserDoc))
		goto LFail;
	if (pvNil == _pstdio)
		{
		SetPortfolioDoc(&fniUserDoc);
		vpcex->EnqueueCid(cidLoadStudioDoc); // this will load the portfolio doc
		}
	else
		{
		// ignore failure 
		_pstdio->FLoadMovie(&fniUserDoc);
		}

	fniTemp.FDelete(); // ignore failure
	return fTrue;
LFail:
	blck.Free(); // so it doesn't reference pfil anymore
	ReleasePpo(&pfil);
	fniTemp.FDelete(); // ignore failure
	return fFalse;
}
#endif //WIN


#ifdef WIN
/***************************************************************************
	Override standard _FGetNextEvt to catch WM_USER event.  Otherwise
	the event will get thrown away, because the event's hwnd is nil.
***************************************************************************/
bool APP::_FGetNextEvt(PEVT pevt)
{
	AssertThis(0);
	AssertVarMem(pevt);

	if (!APP_PAR::_FGetNextEvt(pevt))
		return fFalse;
	if (pevt->message != WM_USER || pevt->wParam != klwOpenDoc)
		return fTrue;
	_FProcessOpenDocCmd(); // ignore failure
	return fFalse; // we've handled the WM_USER event
}
#endif //WIN


/***************************************************************************
	Override default _FastUpdate to optionally skip offscreen buffer
***************************************************************************/
void APP::_FastUpdate(PGOB pgob, PREGN pregnClip, ulong grfapp, PGPT pgpt)
{
	AssertBaseThis(0);

	PMVIE pmvie;

	pmvie = _Pmvie();

	if (_fOnscreenDrawing && pvNil != pmvie && pmvie->FPlaying() &&
		pmvie->Pscen()->Nfrm() != pmvie->Pscen()->NfrmFirst())
		{
		APP_PAR::_FastUpdate(pgob, pregnClip, grfapp | fappOnscreen, pgpt);
		}
	else
		{
		APP_PAR::_FastUpdate(pgob, pregnClip, grfapp, pgpt);
		}
}


#ifdef WIN
/***************************************************************************
	Override default UpdateHwnd to optionally skip offscreen buffer
***************************************************************************/
void APP::UpdateHwnd(HWND hwnd, RC *prc, ulong grfapp)
{
	AssertBaseThis(0); // APP may not be completely valid

	PMVIE pmvie;

	pmvie = _Pmvie();

	if (_fOnscreenDrawing && pvNil != pmvie && pmvie->FPlaying() &&
		pmvie->Pscen()->Nfrm() != pmvie->Pscen()->NfrmFirst())
		{
		APP_PAR::UpdateHwnd(hwnd, prc, grfapp | fappOnscreen);
		}
	else
		{
		APP_PAR::UpdateHwnd(hwnd, prc, grfapp);
		}
}
#endif //WIN


#ifdef WIN

#ifndef WM_DISPLAYCHANGE
#define WM_DISPLAYCHANGE 0x007E   // from winuser.h
#endif // !WM_DISPLAYCHANGE

/***************************************************************************
	Handle Windows messages for the main app window. Return true iff the
	default window proc should _NOT_ be called.
***************************************************************************/
bool APP::_FFrameWndProc(HWND hwnd, uint wm, WPARAM wParam, LPARAM lw,
	long *plwRet)
{
	AssertBaseThis(0);
	AssertVarMem(plwRet);

	switch(wm)
		{
	case WM_ERASEBKGND:
		// Tell windows that we handled the Erase so it doesn't do one.
		// In general we don't want to erase our background ahead of time.
		// This prevents AVIs from flashing.
		*plwRet = fTrue;
		return fTrue;

	case WM_SIZE:
		{
		bool fRet;
		long lwStyle;

		fRet = APP_PAR::_FFrameWndProc(hwnd, wm, wParam, lw, plwRet);
		lwStyle = GetWindowLong(hwnd, GWL_STYLE);
		lwStyle &= ~WS_MAXIMIZEBOX;
		if (wParam == SIZE_MINIMIZED)
			{
			_fMinimized = fTrue;
			if (vpcex != pvNil)
				lwStyle |= WS_SYSMENU;
			else
				lwStyle &= ~WS_SYSMENU;
			}
		else if (!_fRunInWindow)
			lwStyle &= ~WS_SYSMENU;
		SetWindowLong(hwnd, GWL_STYLE, lwStyle);
		if (wParam == SIZE_RESTORED)
			{
			if (_fMainWindowCreated)
				_RebuildMainWindow();
			if (_fSwitchedResolution && _fMinimized)
				{
				if (!_FDisplayIs640480())
					_FSwitch640480(fTrue);
				}
			ShowWindow(vwig.hwndApp, SW_RESTORE); // restore app window
			_fMinimized = fFalse;
			}
		return fRet;
		}
	case WM_DISPLAYCHANGE:
		// Note that we don't need to do any of this if we're closing down
		if (_fQuit)
			break;

		if (_FDisplayIs640480())
			{
			_fRunInWindow = fFalse;
			_RebuildMainWindow();
			}
		else
			{
			// We're not running at 640x480 resolution now. Current design is that
			// if we switch from 640x480 to higher while the app is minimized, the
			// app it to still be full screen when restored. Therefore we don't
			// need to change _fRunInWindow here, as that we remain the same as
			// before the res switch, (as will the Windows properties for the app).
			// All we need to is make a note that we're no longer running in the
			// current windows settings resolution if we're running in full screen.

			if (!_fRunInWindow)
				{
				_fSwitchedResolution = fTrue;

				// If we're not minimized then we must switch to 640x480 resolution.
				// Don't switch res unless we're the active app window

				if (!_fMinimized && GetForegroundWindow() == vwig.hwndApp)
					{
					if (!_FSwitch640480(fTrue))
						_fSwitchedResolution = fFalse;
					}
				}

			// Call rebuild now to make sure the app window gets positioned
			// at the centre of the screen. Note that none of the other
			// window attributes will change beneath _RebuildMainWindow.
			if (!_fMinimized)
				_RebuildMainWindow();
			}
		return fTrue;

	case WM_INITMENUPOPUP:
		{
		// Disable the Close menu item if we are displaying a modal topic.
		// The user can't exit until a modal topic is dismissed.

		bool fDisableClose = (vpappb->CactModal() > (_pcex != pvNil ? 1 : 0));

		EnableMenuItem((HMENU)wParam, SC_CLOSE, MF_BYCOMMAND |
					(fDisableClose ? (MF_DISABLED | MF_GRAYED) : MF_ENABLED));

		break;
		}
		}

	if ((_pstdio == pvNil) || (_pstdio->Pmvie() == pvNil))
		{
	    return APP_PAR::_FFrameWndProc(hwnd, wm, wParam, lw, plwRet);
		}

	switch (wm)
		{
	case WM_QUERY_EXISTS:
		*plwRet = _pstdio->Pmvie()->LwQueryExists(wParam, lw);
		return(fTrue);

	case WM_QUERY_LOCATION:
		*plwRet = _pstdio->Pmvie()->LwQueryLocation(wParam, lw);
		return(fTrue);

	case WM_SET_MOVIE_POS:
		*plwRet = _pstdio->Pmvie()->LwSetMoviePos(wParam, lw);
		return(fTrue);

    default:
	    return APP_PAR::_FFrameWndProc(hwnd, wm, wParam, lw, plwRet);
		}

}
#endif //WIN

/***************************************************************************
 *
 * Returns whether or not screen savers should be allowed.
 *
 * Parameters:
 *  None
 *
 * Returns:
 *  fTrue  - Screen savers should be allowed
 *  fFalse - Screen savers should be blocked
 *
 **************************************************************************/
bool APP::FAllowScreenSaver(void)
{
	AssertBaseThis(0);

	// Disable the screen saver if...
	// 1. We're going to autominimize if a screen saver starts. Otherwise
	//    the user would be confused when they get back to the machine.
	// 2. We've switched resolutions, (ie we're full screen in a > 640x480 mode).
	//    Otherwise the screen save only acts on a portion of the screen.

	return !_FDisplayIs640480() && !_fSwitchedResolution;
}

/***************************************************************************
	Disable the application accelerators
***************************************************************************/
void APP::DisableAccel(void)
{
	AssertBaseThis(0); // Gets called from destructors

	if (_cactDisable == 0)
		{
#ifdef WIN
		_haccel = vwig.haccel;
		vwig.haccel = _haccelGlobal;
#else
		RawRtn();
#endif
		}

	_cactDisable++;

}

/***************************************************************************
	Enable the application accelerators
***************************************************************************/
void APP::EnableAccel(void)
{
	AssertBaseThis(0); // Gets called from destructors
	Assert(_cactDisable > 0, "Enable called w/o a disable");

	_cactDisable--;

	if (_cactDisable == 0)
		{
#ifdef WIN
		vwig.haccel = _haccel;
#else
		RawRtn();
#endif
		}

}

/***************************************************************************
 *
 * Handle disable accelerator command
 *
 * Parameters:
 *  pcmd - Pointer to the command to process.
 *
 * Returns:
 *  fTrue if it handled the command, else fFalse.
 *
 **************************************************************************/
bool APP::FCmdDisableAccel(PCMD pcmd)
{
	AssertThis(0);
	AssertVarMem(pcmd);
	DisableAccel();
	return fTrue;
}


/***************************************************************************
 *
 * Handle enable accelerator command
 *
 * Parameters:
 *  pcmd - Pointer to the command to process.
 *
 * Returns:
 *  fTrue if it handled the command, else fFalse.
 *
 **************************************************************************/
bool APP::FCmdEnableAccel(PCMD pcmd)
{
	AssertThis(0);
	AssertVarMem(pcmd);
	EnableAccel();
	return fTrue;
}


/******************************************************************************
	FCmdInvokeSplot
		Invokes the splot machine.
	
	Arguments:
		PCMD pcmd
			rglw[0]  --  contains the GOB id of the parent of the Splot machine
			rglw[1]  --  contains the GOB id of the Splot machine itself
	
	Returns: fTrue always
	
************************************************************ PETED ***********/
bool APP::FCmdInvokeSplot(PCMD pcmd)
{
	AssertThis(0);
	AssertVarMem(pcmd);

	PSPLOT psplot;

	psplot = SPLOT::PsplotNew(pcmd->rglw[0], pcmd->rglw[1], _pcrmAll);
	if (psplot == pvNil)
		PushErc(ercSocCantInitSplot);

	return fTrue;
}


/***************************************************************************
	Handoff a movie to the app so it can pass it on to the studio
***************************************************************************/
void APP::HandoffMovie(PMVIE pmvie)
{
	AssertThis(0);
	AssertPo(pmvie, 0);

	ReleasePpo(&_pmvieHandoff);
	_pmvieHandoff = pmvie;
	_pmvieHandoff->AddRef();
}

/***************************************************************************
	Grab the APP movie
***************************************************************************/
PMVIE APP::PmvieRetrieve(void)
{
	AssertThis(0);

	PMVIE pmvie = _pmvieHandoff;

	_pmvieHandoff = pvNil;  //  Caller now owns this pointer
	return pmvie;
}


#ifdef BUG1085
/******************************************************************************
	HideCurs
	ShowCurs
	PushCurs
	PopCurs
	
	    Some simple cursor restoration functionality, for use when a modal
		topic comes up.  Assumes that you won't try to mess with the cursor
		state while you've got the old cursor state pushed, and that you won't
		try to push the cursor state while you've already got the old cursor
		state pushed.
	
************************************************************ PETED ***********/
void APP::HideCurs(void)
{
	AssertThis(0);

	Assert(_cactCursHide != ivNil, "Can't hide cursor in Push/PopCurs pair");
	_cactCursHide++;
	APP_PAR::HideCurs();
}

void APP::ShowCurs(void)
{
	AssertThis(0);

	Assert(_cactCursHide > 0, "Unbalanced ShowCurs call");
	_cactCursHide--;
	APP_PAR::ShowCurs();
}

void APP::PushCurs(void)
{
	AssertThis(0);

	Assert(_cactCursHide != ivNil, "Can't nest cursor restoration");
	_cactCursSav = _cactCursHide;
	while (_cactCursHide)
		ShowCurs();
	_cactCursHide = ivNil;
}

void APP::PopCurs(void)
{
	AssertThis(0);

	Assert(_cactCursHide == ivNil, "Unbalanced cursor restoration");
	_cactCursHide = 0;
	while (_cactCursHide < _cactCursSav)
		HideCurs();
}
#endif // BUG1085


#ifdef DEBUG
/***************************************************************************
	Assert the validity of the APP
***************************************************************************/
void APP::AssertValid(ulong grf)
{
	APP_PAR::AssertValid(0);
	AssertNilOrPo(_pstdio, 0);
	AssertNilOrPo(_ptatr, 0);
	AssertNilOrPo(_pmvieHandoff, 0);
	AssertPo(_pcfl, 0);
	AssertPo(_pgstStudioFiles, 0);
	AssertPo(_pgstBuildingFiles, 0);
	AssertPo(_pgstSharedFiles, 0);
	AssertPo(_pgstApp, 0);
	AssertPo(_pkwa, 0);
	AssertPo(_pgstApp, 0);
	AssertPo(_pcrmAll, 0);
	AssertPo(_pglicrfBuilding, 0);
	AssertPo(_pglicrfStudio, 0);
	AssertNilOrPo(_pcex, 0);
}

/***************************************************************************
	Mark memory used by the APP
***************************************************************************/
void APP::MarkMem(void)
{
	AssertThis(0);
	APP_PAR::MarkMem();
	MarkMemObj(vptagm);
	MTRL::MarkShadeTable();
	TDT::MarkActionNames();
	MarkMemObj(_pstdio);
	MarkMemObj(_ptatr);
	MarkMemObj(_pmvieHandoff);
	MarkMemObj(_pcfl);
	MarkMemObj(_pgstStudioFiles);
	MarkMemObj(_pgstBuildingFiles);
	MarkMemObj(_pgstSharedFiles);
	MarkMemObj(_pgstApp);
	MarkMemObj(_pkwa);
	MarkMemObj(_pgstApp);
	MarkMemObj(_pcrmAll);
	MarkMemObj(_pglicrfBuilding);
	MarkMemObj(_pglicrfStudio);
	MarkMemObj(_pcex);
}
#endif // DEBUG


//
//
//
//  KWA (KidWorld for App) stuff begins here
//
//
//


/***************************************************************************
	KWA destructor
***************************************************************************/
KWA::~KWA(void)
{
	ReleasePpo(&_pmbmp);
}


/***************************************************************************
	Set the KWA's MBMP (for splash screen)
***************************************************************************/
void KWA::SetMbmp(PMBMP pmbmp)
{
	AssertThis(0);
	AssertNilOrPo(pmbmp, 0);

	RC rc;

	if (pvNil != pmbmp)
		pmbmp->AddRef();
	ReleasePpo(&_pmbmp);
	_pmbmp = pmbmp;
	GetRcVis(&rc, cooLocal);
	vpappb->MarkRc(&rc, this);
}

					
/***************************************************************************
	Draw the KWA's MBMP, if any (for splash screen)
***************************************************************************/
void KWA::Draw(PGNV pgnv, RC *prcClip)
{
	AssertThis(0);
	AssertPo(pgnv, 0);
	AssertVarMem(prcClip);

	if (pvNil != _pmbmp)
		pgnv->DrawMbmp(_pmbmp, 0, 0);
}


/***************************************************************************
	Find a file given a string.
***************************************************************************/
bool KWA::FFindFile(PSTN pstnSrc, PFNI pfni)
{
	AssertThis(0);
	AssertPo(pstnSrc, 0);
	AssertPo(pfni, 0);

	return vptagm->FFindFile(vapp.SidProduct(), pstnSrc, pfni, FAskForCD());
}

/***************************************************************************
	Do a modal help topic.
***************************************************************************/
bool KWA::FModalTopic(PRCA prca, CNO cnoTopic, long *plwRet)
{
	AssertThis(0);
	AssertPo(prca, 0);
	AssertVarMem(plwRet);

	bool fRet;

	// Take any special action here if necessary, before the
	// modal help topic is displayed. (Eg, disable help keys).

	// Now take the default action.
#ifdef BUG1085
	vapp.PushCurs();
	fRet = KWA_PAR::FModalTopic(prca, cnoTopic, plwRet);
	vapp.PopCurs();
#else
	fRet = KWA_PAR::FModalTopic(prca, cnoTopic, plwRet);
#endif  // !BUG1085

	// Let script know that the modal topic has been dismissed.
	// This is required for the projects.
	vpcex->EnqueueCid(cidModalTopicClosed, 0, 0, *plwRet);

	return fRet;
}

#ifdef DEBUG
/***************************************************************************
	Assert the validity of the KWA
***************************************************************************/
void KWA::AssertValid(ulong grf)
{
	KWA_PAR::AssertValid(0);
	AssertNilOrPo(_pmbmp, 0);
}


/***************************************************************************
	Mark memory used by the KWA
***************************************************************************/
void KWA::MarkMem(void)
{
	AssertThis(0);
	KWA_PAR::MarkMem();
	MarkMemObj(_pmbmp);
}
#endif // DEBUG
